@quadiusai/dsh-greenharness 0.0.0-stage → 0.1.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,40 @@
1
+ {
2
+ "description": "The mock preset with the NeMo-style thinking switch on both routes: chat_template_kwargs.enable_thinking follows the tier's reasoning effort. It checks the request body that a chat-template server such as vLLM would receive. Still a mock, not a model.",
3
+ "mock": true,
4
+ "routes": {
5
+ "mock-small": {
6
+ "baseURL": "http://127.0.0.1:8124/v1",
7
+ "api": "openai-completions",
8
+ "displayName": "Mock small",
9
+ "compat": {
10
+ "thinkingFormat": "chat-template",
11
+ "chatTemplateKwargs": { "enable_thinking": { "$var": "thinking.enabled" } }
12
+ },
13
+ "models": [{
14
+ "id": "mock-bolt-e4b",
15
+ "name": "Mock Bolt E4B",
16
+ "contextWindow": 65536,
17
+ "reasoningEfforts": { "off": null, "high": "high" }
18
+ }]
19
+ },
20
+ "mock-large": {
21
+ "baseURL": "http://127.0.0.1:8124/v1",
22
+ "api": "openai-completions",
23
+ "displayName": "Mock large",
24
+ "compat": {
25
+ "thinkingFormat": "chat-template",
26
+ "chatTemplateKwargs": { "enable_thinking": { "$var": "thinking.enabled" } }
27
+ },
28
+ "models": [{
29
+ "id": "mock-lightning-30b",
30
+ "name": "Mock Lightning",
31
+ "contextWindow": 262144,
32
+ "reasoningEfforts": { "off": null, "high": "high" }
33
+ }]
34
+ }
35
+ },
36
+ "tiers": {
37
+ "small": { "provider": "mock-small", "model": "mock-bolt-e4b", "reasoningEffort": "off" },
38
+ "large": { "provider": "mock-large", "model": "mock-lightning-30b", "reasoningEffort": "high" }
39
+ }
40
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "description": "Scripted mock server on 127.0.0.1. It checks routing and context edits only; it is not a model and measures no quality.",
3
+ "mock": true,
4
+ "routes": {
5
+ "mock-small": {
6
+ "baseURL": "http://127.0.0.1:8124/v1",
7
+ "api": "openai-completions",
8
+ "displayName": "Mock small",
9
+ "models": [{
10
+ "id": "mock-bolt-e4b",
11
+ "name": "Mock Bolt E4B",
12
+ "contextWindow": 65536,
13
+ "reasoningEfforts": { "off": null, "high": "high" }
14
+ }]
15
+ },
16
+ "mock-large": {
17
+ "baseURL": "http://127.0.0.1:8124/v1",
18
+ "api": "openai-completions",
19
+ "displayName": "Mock large",
20
+ "models": [{
21
+ "id": "mock-lightning-30b",
22
+ "name": "Mock Lightning",
23
+ "contextWindow": 262144,
24
+ "reasoningEfforts": { "off": null, "high": "high" }
25
+ }]
26
+ }
27
+ },
28
+ "tiers": {
29
+ "small": { "provider": "mock-small", "model": "mock-bolt-e4b", "reasoningEffort": "off" },
30
+ "large": { "provider": "mock-large", "model": "mock-lightning-30b", "reasoningEffort": "high" }
31
+ }
32
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "description": "Live NeMo models on a Brev instance, reached through port-forwards on localhost. The large tier is Nemotron 3.5 Lightning 30B-A3B (BF16). The small tier is Nemotron 3.5 Bolt E4B, whose model id, serve command and thinking switch are NOT verified; see bench/BREV.md before trusting its numbers.",
3
+ "mock": false,
4
+ "routes": {
5
+ "nemo-bolt-e4b": {
6
+ "baseURL": "http://127.0.0.1:8001/v1",
7
+ "api": "openai-completions",
8
+ "displayName": "Nemotron 3.5 Bolt E4B (Brev, unverified)",
9
+ "compat": {
10
+ "thinkingFormat": "chat-template",
11
+ "chatTemplateKwargs": { "enable_thinking": { "$var": "thinking.enabled" } }
12
+ },
13
+ "models": [{
14
+ "id": "nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-4GB",
15
+ "name": "Nemotron 3.5 Bolt E4B",
16
+ "contextWindow": 65536,
17
+ "reasoningEfforts": { "off": null, "high": "high" }
18
+ }]
19
+ },
20
+ "nemo-lightning-30b": {
21
+ "baseURL": "http://127.0.0.1:8000/v1",
22
+ "api": "openai-completions",
23
+ "displayName": "Nemotron 3.5 Lightning 30B-A3B (Brev)",
24
+ "compat": {
25
+ "thinkingFormat": "chat-template",
26
+ "chatTemplateKwargs": { "enable_thinking": { "$var": "thinking.enabled" } }
27
+ },
28
+ "models": [{
29
+ "id": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16",
30
+ "name": "Nemotron 3.5 Lightning 30B-A3B",
31
+ "contextWindow": 262144,
32
+ "reasoningEfforts": { "off": null, "high": "high" }
33
+ }]
34
+ }
35
+ },
36
+ "tiers": {
37
+ "small": { "provider": "nemo-bolt-e4b", "model": "nvidia/NVIDIA-Nemotron-3.5-Bolt-E4B-4GB", "reasoningEffort": "off" },
38
+ "large": { "provider": "nemo-lightning-30b", "model": "nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-BF16", "reasoningEffort": "high" }
39
+ }
40
+ }
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Run one benchmark case through the real DSH headless runner and report what happened.
4
+ *
5
+ * node bench/run-case.mjs --task subtract-bug --arm greenharness --scenario subtract-fix --out <dir>
6
+ * node bench/run-case.mjs --task subtract-bug --arm always-large --preset nemo-brev --out <dir>
7
+ *
8
+ * Arms:
9
+ * greenharness DSH with the GreenHarness plugin: the small tier by default, escalating on verifier failures.
10
+ * always-large DSH without the plugin; every model call uses the large tier (the single-model baseline).
11
+ * always-small DSH without the plugin; every model call uses the small tier.
12
+ *
13
+ * Presets (bench/presets/<name>.json) name the routes and the two tiers. A preset with "mock": true
14
+ * (such as `mock`) points its routes at a scripted OpenAI-compatible server (plumbing only, not a
15
+ * model) and needs --scenario. Live presets serve real models and run the task from its TASK.md;
16
+ * they take no --scenario.
17
+ *
18
+ * Environment: DSH_BIN (the dsh binary, default `dsh`), NEMO_API_KEY (default: a placeholder for
19
+ * local servers that do not check keys).
20
+ *
21
+ * Writes under --out: workspace/ (the task after the run), patch.yml, ledger.jsonl, requests.jsonl
22
+ * (mock only), stdout.txt, stderr.txt and summary.json. The verdict is an independent re-run of
23
+ * `node --test` in the workspace, not the agent's own claim.
24
+ */
25
+ import { execFile } from 'node:child_process'
26
+ import { existsSync, readFileSync } from 'node:fs'
27
+ import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
28
+ import { dirname, join, resolve } from 'node:path'
29
+ import { fileURLToPath } from 'node:url'
30
+ import { startMockServer } from './mock-openai-server.mjs'
31
+ import { renderPatch } from './lib/patch.mjs'
32
+
33
+ const here = dirname(fileURLToPath(import.meta.url))
34
+ const pluginRoot = resolve(here, '..')
35
+ const ARMS = ['greenharness', 'always-large', 'always-small']
36
+ const DEFAULT_PROMPT = 'Fix the failing tests in this project. Run node --test to check your work.'
37
+
38
+ async function main() {
39
+ const options = parseArgs(process.argv.slice(2))
40
+ const { task, arm, scenario, out, preset = 'mock', port = '8124' } = options
41
+ if (!task || !out || !ARMS.includes(arm)) {
42
+ console.error(`usage: node bench/run-case.mjs --task <dir under bench/tasks> --arm <${ARMS.join('|')}> --out <dir> [--preset mock|nemo-brev] [--scenario <name>] [--port 8124]`)
43
+ process.exitCode = 2
44
+ return
45
+ }
46
+
47
+ const outDir = resolve(out)
48
+ await rm(outDir, { recursive: true, force: true })
49
+ await mkdir(outDir, { recursive: true })
50
+ const workspace = join(outDir, 'workspace')
51
+ await cp(join(here, 'tasks', task), workspace, { recursive: true })
52
+ const prompt = existsSync(join(workspace, 'TASK.md')) ? await readFile(join(workspace, 'TASK.md'), 'utf8') : DEFAULT_PROMPT
53
+
54
+ const spec = JSON.parse(await readFile(join(here, 'presets', `${preset}.json`), 'utf8'))
55
+ const routes = structuredClone(spec.routes)
56
+ let mock
57
+ if (spec.mock === true) {
58
+ if (!scenario) throw new Error('a mock preset needs --scenario <name>')
59
+ const script = JSON.parse(await readFile(join(here, 'scenarios', `${scenario}.json`), 'utf8'))
60
+ mock = await startMockServer({ port: Number(port), steps: script.steps, logPath: join(outDir, 'requests.jsonl') })
61
+ for (const route of Object.values(routes)) route.baseURL = mock.url
62
+ }
63
+
64
+ const tiers = arm === 'always-large' ? { small: spec.tiers.large, large: spec.tiers.large }
65
+ : arm === 'always-small' ? { small: spec.tiers.small, large: spec.tiers.small }
66
+ : spec.tiers
67
+ const patchPath = join(outDir, 'patch.yml')
68
+ await writeFile(patchPath, renderPatch({
69
+ routes,
70
+ tiers,
71
+ greenharness: arm === 'greenharness',
72
+ pluginEntry: join(pluginRoot, 'src', 'index.mjs'),
73
+ ledgerPath: join(outDir, 'ledger.jsonl'),
74
+ }))
75
+
76
+ const started = Date.now()
77
+ let session
78
+ try {
79
+ session = await run(process.env.DSH_BIN ?? 'dsh', ['headless', '--patch', patchPath, prompt], {
80
+ cwd: workspace,
81
+ env: { ...process.env, DSH_HOME: join(outDir, 'dsh-home'), NEMO_API_KEY: process.env.NEMO_API_KEY ?? 'local-dev-no-auth' },
82
+ timeout: 15 * 60 * 1000,
83
+ })
84
+ } finally {
85
+ if (mock) await mock.close()
86
+ }
87
+ const wallSeconds = (Date.now() - started) / 1000
88
+ await writeFile(join(outDir, 'stdout.txt'), session.stdout)
89
+ await writeFile(join(outDir, 'stderr.txt'), session.stderr)
90
+
91
+ // TAP output has the `# pass N` lines that matchCount reads; the default reporter does not.
92
+ const suite = await run(process.execPath, ['--test', '--test-reporter=tap'], { cwd: workspace, timeout: 5 * 60 * 1000 })
93
+ const ledger = readJsonl(join(outDir, 'ledger.jsonl'))
94
+ const requests = mock ? readJsonl(join(outDir, 'requests.jsonl')) : []
95
+ // A problem makes the run fail loudly. Without these checks, a wrong DSH_BIN or a plugin that
96
+ // did not load would look like a finished run, and the greenharness arm would quietly be a baseline.
97
+ const problems = []
98
+ if (session.code !== 0) problems.push(`DSH exited with code ${session.code}; read stderr.txt`)
99
+ if (arm === 'greenharness' && !ledger.some(record => record.type === 'request')) {
100
+ problems.push('GreenHarness recorded no model calls, so the plugin probably did not load; read stderr.txt for "failed to import" or "did not activate"')
101
+ }
102
+ const summary = {
103
+ arm,
104
+ task,
105
+ preset,
106
+ scenario: scenario ?? null,
107
+ wallSeconds,
108
+ dshExitCode: session.code,
109
+ dshTimedOut: session.timedOut,
110
+ problems,
111
+ suite: { exitCode: suite.code, pass: matchCount(suite.stdout, 'pass'), fail: matchCount(suite.stdout, 'fail') },
112
+ finalText: session.stdout.trim(),
113
+ ledger: summarizeLedger(ledger),
114
+ ...(mock ? { mock: summarizeMock(requests) } : {}),
115
+ }
116
+ await writeFile(join(outDir, 'summary.json'), `${JSON.stringify(summary, null, 2)}\n`)
117
+ console.log(JSON.stringify(summary, null, 2))
118
+ for (const problem of problems) console.error(`problem: ${problem}`)
119
+ if (problems.length > 0) process.exitCode = 1
120
+ }
121
+
122
+ /**
123
+ * Run a command and resolve with its exit code and output. Never rejects.
124
+ * @param {string} command
125
+ * @param {string[]} args
126
+ * @param {import('node:child_process').ExecFileOptions} [options]
127
+ */
128
+ function run(command, args, options = {}) {
129
+ return new Promise((done) => {
130
+ execFile(command, args, { maxBuffer: 256 * 1024 * 1024, ...options }, (error, stdout, stderr) => {
131
+ // A program that never started (for example, a wrong DSH_BIN) has no numeric exit code.
132
+ // Keep its message, or stderr.txt stays empty and the failure is invisible.
133
+ const startFailure = error && typeof error.code !== 'number' ? `${error.message}\n` : ''
134
+ done({
135
+ code: error ? (typeof error.code === 'number' ? error.code : 1) : 0,
136
+ stdout: String(stdout ?? ''),
137
+ stderr: String(stderr ?? '') + startFailure,
138
+ timedOut: error?.killed === true,
139
+ })
140
+ })
141
+ })
142
+ }
143
+
144
+ /**
145
+ * @param {string} text - TAP output.
146
+ * @param {'pass' | 'fail'} kind
147
+ */
148
+ function matchCount(text, kind) {
149
+ const match = new RegExp(`^# ${kind} (\\d+)$`, 'm').exec(text)
150
+ return match ? Number(match[1]) : null
151
+ }
152
+
153
+ /** @param {string} path */
154
+ function readJsonl(path) {
155
+ if (!existsSync(path)) return []
156
+ return readFileSync(path, 'utf8').split('\n').filter(line => line.trim() !== '').map(line => JSON.parse(line))
157
+ }
158
+
159
+ /**
160
+ * What the ledger says the run cost and did.
161
+ * @param {Array<Record<string, any>>} records
162
+ */
163
+ function summarizeLedger(records) {
164
+ const requests = records.filter(r => r.type === 'request')
165
+ const tools = records.filter(r => r.type === 'tool')
166
+ const usage = { inputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, outputTokens: 0 }
167
+ for (const request of requests) {
168
+ for (const key of Object.keys(usage)) usage[key] += request.usage?.[key] ?? 0
169
+ }
170
+ return {
171
+ model_calls: requests.length,
172
+ tiers: countBy(requests, r => r.tier),
173
+ models: countBy(requests, r => `${r.provider}/${r.model}`),
174
+ usage,
175
+ costUsd: sum(requests, r => (Number.isFinite(r.costUsd) ? r.costUsd : 0)),
176
+ route_changes: records.filter(r => r.type === 'route').map(r => `${r.from}->${r.to} (${r.reason})`),
177
+ loop_breaks: records.filter(r => r.type === 'loop-break').length,
178
+ tool_actions: countBy(tools, r => `${r.tool}:${r.action}`),
179
+ errors: records.filter(r => r.type === 'error').map(r => `${r.stage}: ${r.message}`),
180
+ }
181
+ }
182
+
183
+ /**
184
+ * What the mock server saw: which model served each agent call, and what the model was shown.
185
+ * @param {Array<Record<string, any>>} records
186
+ */
187
+ function summarizeMock(records) {
188
+ const agent = records.filter(r => r.agentCall)
189
+ const toolResults = records.flatMap(r => r.toolMessages ?? [])
190
+ return {
191
+ agent_calls: agent.length,
192
+ side_calls: records.length - agent.length,
193
+ served_models: countBy(agent, r => r.model),
194
+ prompt_tokens_est: sum(agent, r => r.promptTokens),
195
+ cached_tokens_est: sum(agent, r => r.cachedTokens),
196
+ model_notices_sent: sum(records, r => r.noticeCount ?? 0),
197
+ saw_distill_footer: toolResults.some(t => t.includes('[greenharness: distilled')),
198
+ saw_dedupe_stub: toolResults.some(t => t.includes('[greenharness: this read returns the same lines')),
199
+ saw_loop_guard_denial: toolResults.some(t => t.includes('GreenHarness loop guard')),
200
+ }
201
+ }
202
+
203
+ /**
204
+ * @template T
205
+ * @param {T[]} items
206
+ * @param {(item: T) => unknown} key
207
+ */
208
+ function countBy(items, key) {
209
+ const counts = {}
210
+ for (const item of items) {
211
+ const name = String(key(item))
212
+ counts[name] = (counts[name] ?? 0) + 1
213
+ }
214
+ return counts
215
+ }
216
+
217
+ /**
218
+ * @template T
219
+ * @param {T[]} items
220
+ * @param {(item: T) => unknown} pick
221
+ */
222
+ function sum(items, pick) {
223
+ return items.reduce((total, item) => total + (Number(pick(item)) || 0), 0)
224
+ }
225
+
226
+ /** @param {string[]} argv */
227
+ function parseArgs(argv) {
228
+ const options = {}
229
+ for (let i = 0; i < argv.length; i += 2) {
230
+ const key = argv[i]
231
+ if (!key?.startsWith('--') || argv[i + 1] === undefined) {
232
+ throw new Error(`expected --name value pairs, got: ${argv.slice(i).join(' ')}`)
233
+ }
234
+ options[key.slice(2)] = argv[i + 1]
235
+ }
236
+ return options
237
+ }
238
+
239
+ main().catch((error) => {
240
+ console.error(error)
241
+ process.exitCode = 1
242
+ })
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Runs every scripted scenario under every arm and prints one table. It is a plumbing check: the
4
+ * mock is scripted, so the numbers show routing and context edits, not model quality or real cost.
5
+ *
6
+ * npm run bench:mock (needs Node 24 and DSH: set DSH_BIN, or put `dsh` on PATH)
7
+ *
8
+ * Results land under BENCH_OUT (default: runs/mock). Each case also writes its own summary.json.
9
+ */
10
+ import { execFile } from 'node:child_process'
11
+ import { readFile, readdir, writeFile, mkdir } from 'node:fs/promises'
12
+ import { dirname, join, resolve } from 'node:path'
13
+ import { fileURLToPath } from 'node:url'
14
+
15
+ const here = dirname(fileURLToPath(import.meta.url))
16
+ const outRoot = resolve(process.env.BENCH_OUT ?? join(here, '..', 'runs', 'mock'))
17
+ const ARMS = ['greenharness', 'always-large', 'always-small']
18
+
19
+ const scenarios = (await readdir(join(here, 'scenarios')))
20
+ .filter(name => name.endsWith('.json'))
21
+ .map(name => name.replace(/\.json$/, ''))
22
+ .sort()
23
+
24
+ const rows = []
25
+ for (const scenario of scenarios) {
26
+ for (const arm of ARMS) {
27
+ const out = join(outRoot, `${scenario}-${arm}`)
28
+ await runCase(['--task', 'subtract-bug', '--arm', arm, '--scenario', scenario, '--out', out, '--preset', 'mock'])
29
+ rows.push(await readRow(scenario, arm, join(out, 'summary.json')))
30
+ }
31
+ }
32
+
33
+ const table = renderTable(rows)
34
+ await mkdir(outRoot, { recursive: true })
35
+ await writeFile(join(outRoot, 'SUMMARY.md'), `${table}\n\nMock token counts are characters divided by four. They are not a tokenizer and not a bill.\n`)
36
+ console.log(table)
37
+
38
+ /** @param {string[]} args */
39
+ function runCase(args) {
40
+ return new Promise((done) => {
41
+ execFile(process.execPath, [join(here, 'run-case.mjs'), ...args], { env: process.env, maxBuffer: 64 * 1024 * 1024 }, (error, _stdout, stderr) => {
42
+ if (error) console.error(`case failed: ${args.join(' ')}\n${stderr}`)
43
+ done()
44
+ })
45
+ })
46
+ }
47
+
48
+ /**
49
+ * @param {string} scenario
50
+ * @param {string} arm
51
+ * @param {string} path
52
+ */
53
+ async function readRow(scenario, arm, path) {
54
+ try {
55
+ const summary = JSON.parse(await readFile(path, 'utf8'))
56
+ const served = summary.mock?.served_models ?? {}
57
+ return {
58
+ scenario,
59
+ arm,
60
+ calls: summary.mock?.agent_calls ?? 0,
61
+ served: `${served['mock-bolt-e4b'] ?? 0} small / ${served['mock-lightning-30b'] ?? 0} large`,
62
+ promptEst: summary.mock?.prompt_tokens_est ?? 0,
63
+ cachedEst: summary.mock?.cached_tokens_est ?? 0,
64
+ suite: summary.suite?.fail === null ? 'not run' : `${summary.suite.pass} pass / ${summary.suite.fail} fail`,
65
+ distilled: summary.ledger?.tool_actions?.['bash:distill'] ?? 0,
66
+ deduped: summary.ledger?.tool_actions?.['read:dedupe'] ?? 0,
67
+ routes: summary.ledger?.route_changes?.length ?? 0,
68
+ loopDenied: summary.mock?.saw_loop_guard_denial === true,
69
+ errors: summary.ledger?.errors?.length ?? 0,
70
+ }
71
+ } catch (error) {
72
+ return { scenario, arm, error: String(error?.message ?? error) }
73
+ }
74
+ }
75
+
76
+ /** @param {Array<Record<string, any>>} rows */
77
+ function renderTable(rows) {
78
+ const header = '| scenario | arm | agent calls | served by | prompt tokens (est.) | cached (est.) | suite | distilled | deduped | route changes | loop denied | errors |'
79
+ const rule = '|---|---|---:|---|---:|---:|---|---:|---:|---:|---|---:|'
80
+ const body = rows.map((row) => {
81
+ if (row.error) return `| ${row.scenario} | ${row.arm} | error: ${row.error} |`
82
+ return `| ${row.scenario} | ${row.arm} | ${row.calls} | ${row.served} | ${row.promptEst} | ${row.cachedEst} | ${row.suite} | ${row.distilled} | ${row.deduped} | ${row.routes} | ${row.loopDenied ? 'yes' : 'no'} | ${row.errors} |`
83
+ })
84
+ return [header, rule, ...body].join('\n')
85
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "description": "Keep re-running a failing command with no edits. The loop breaker should deny the repeat after three identical failures.",
3
+ "steps": [
4
+ { "tool_calls": [{ "name": "bash", "arguments": { "command": "node --test", "description": "Run the test suite" } }] },
5
+ { "tool_calls": [{ "name": "bash", "arguments": { "command": "node --test", "description": "Run the test suite" } }] },
6
+ { "tool_calls": [{ "name": "bash", "arguments": { "command": "node --test", "description": "Run the test suite" } }] },
7
+ { "tool_calls": [{ "name": "bash", "arguments": { "command": "node --test", "description": "Run the test suite" } }] },
8
+ { "tool_calls": [{ "name": "bash", "arguments": { "command": "node --test", "description": "Run the test suite" } }] },
9
+ { "content": "Stopping: the tests keep failing." }
10
+ ]
11
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "description": "Run the suite (fails), read the source twice (the second read should dedupe), fix subtract with an exact edit, rerun twice (passes), then finish.",
3
+ "steps": [
4
+ { "tool_calls": [{ "name": "bash", "arguments": { "command": "node --test", "description": "Run the test suite" } }] },
5
+ { "tool_calls": [{ "name": "read", "arguments": { "file_path": "src/math.mjs" } }] },
6
+ { "tool_calls": [{ "name": "read", "arguments": { "file_path": "src/math.mjs" } }] },
7
+ { "tool_calls": [{ "name": "edit", "arguments": { "file_path": "src/math.mjs", "old_string": "export function subtract(a, b) {\n return a + b", "new_string": "export function subtract(a, b) {\n return a - b" } }] },
8
+ { "tool_calls": [{ "name": "bash", "arguments": { "command": "node --test", "description": "Run the test suite" } }] },
9
+ { "tool_calls": [{ "name": "bash", "arguments": { "command": "node --test", "description": "Run the test suite" } }] },
10
+ { "content": "Fixed subtract to return a - b. All tests pass." }
11
+ ]
12
+ }
@@ -0,0 +1,2 @@
1
+ The `subtract` function in `src/math.mjs` returns the wrong result. Make `node --test` pass
2
+ without changing the tests. Keep the change minimal.
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "subtract-bug",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "node --test"
8
+ }
9
+ }
@@ -0,0 +1,8 @@
1
+ export function add(a, b) {
2
+ return a + b
3
+ }
4
+
5
+ // Bug: subtract should return a - b.
6
+ export function subtract(a, b) {
7
+ return a + b
8
+ }
@@ -0,0 +1,11 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { add, subtract } from '../src/math.mjs'
4
+
5
+ test('adds two numbers', () => {
6
+ assert.equal(add(2, 3), 5)
7
+ })
8
+
9
+ test('subtracts b from a', () => {
10
+ assert.equal(subtract(5, 3), 2)
11
+ })
@@ -0,0 +1,9 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ // Passing filler that makes the raw runner output long, so distillation has work to do.
5
+ for (let i = 0; i < 300; i += 1) {
6
+ test(`filler case ${i}`, () => {
7
+ assert.ok(i >= 0)
8
+ })
9
+ }
@@ -0,0 +1,4 @@
1
+ # Mounted by `dsh plugin --profile <name> add @quadiusai/dsh-greenharness`. Every setting has a default.
2
+ - insert:
3
+ - id: greenharness
4
+ name: '@quadiusai/dsh-greenharness'
package/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "@quadiusai/dsh-greenharness",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "GreenHarness: a DeepSeek Harness (dsh) plugin that cuts token, GPU and dollar cost in multi-step agent workflows. Install with one command; control it with /greenharness.",
5
+ "type": "module",
6
+ "main": "src/index.mjs",
7
+ "exports": {
8
+ ".": "./src/index.mjs"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "bench",
13
+ "cordis.patch.yml",
14
+ "README.md",
15
+ "GETTING-STARTED.md"
16
+ ],
17
+ "scripts": {
18
+ "test": "node --test test/*.test.mjs",
19
+ "bench:mock": "node bench/run-mock-suite.mjs",
20
+ "mock-server": "node bench/mock-openai-server.mjs"
21
+ },
22
+ "engines": {
23
+ "node": ">=22.19.0"
24
+ },
25
+ "keywords": [
26
+ "dsh",
27
+ "deepseek-harness",
28
+ "cordis",
29
+ "agents",
30
+ "nvidia",
31
+ "nemotron",
32
+ "finops",
33
+ "green-ai"
34
+ ],
35
+ "license": "MIT",
36
+ "dsh": {
37
+ "bundle": {
38
+ "patch": "./cordis.patch.yml"
39
+ }
40
+ },
41
+ "dependencies": {
42
+ "@deepseek-ai/schemastery": "~3.18.5"
43
+ }
44
+ }