devlyn-cli 2.2.0 → 2.2.2
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/benchmark/auto-resolve/README.md +7 -4
- package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F10-persist-write-collision/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F11-batch-import-all-or-nothing/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F12-webhook-raw-body-signature/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F15-frozen-diff-race-review/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F2-cli-medium-subcommand/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F21-cli-scheduler-priority/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F22-cli-ledger-close/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F23-cli-fulfillment-wave/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F26-cli-payout-ledger-rules/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F3-backend-contract-risk/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F4-web-browser-design/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F5-fix-loop-red-green/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F6-dep-audit-native-module/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F7-out-of-scope-trap/spec.md +0 -1
- package/benchmark/auto-resolve/fixtures/F9-e2e-ideate-to-resolve/spec.md +0 -3
- package/benchmark/auto-resolve/scripts/oracle-scope-tier-a.py +0 -11
- package/benchmark/auto-resolve/scripts/oracle-scope-tier-b.py +0 -10
- package/benchmark/auto-resolve/scripts/run-fixture.sh +23 -3
- package/config/skills/_shared/spec-verify-check.py +51 -0
- package/config/skills/devlyn:resolve/SKILL.md +12 -5
- package/config/skills/devlyn:resolve/references/phases/probe-derive.md +24 -5
- package/package.json +1 -1
- package/scripts/lint-skills.sh +39 -22
- package/benchmark/auto-resolve/fixtures/F27-cli-gift-card-redemption/NOTES.md +0 -24
- package/benchmark/auto-resolve/fixtures/F27-cli-gift-card-redemption/expected.json +0 -66
- package/benchmark/auto-resolve/fixtures/F27-cli-gift-card-redemption/metadata.json +0 -10
- package/benchmark/auto-resolve/fixtures/F27-cli-gift-card-redemption/setup.sh +0 -22
- package/benchmark/auto-resolve/fixtures/F27-cli-gift-card-redemption/spec.md +0 -62
- package/benchmark/auto-resolve/fixtures/F27-cli-gift-card-redemption/task.txt +0 -9
- package/benchmark/auto-resolve/fixtures/F27-cli-gift-card-redemption/verifiers/exact-success.js +0 -48
- package/benchmark/auto-resolve/fixtures/F27-cli-gift-card-redemption/verifiers/insufficient-balance.js +0 -36
- package/benchmark/auto-resolve/fixtures/F27-cli-gift-card-redemption/verifiers/rules-source.js +0 -55
- package/benchmark/auto-resolve/fixtures/F28-cli-rental-quote-rules/NOTES.md +0 -20
- package/benchmark/auto-resolve/fixtures/F28-cli-rental-quote-rules/expected.json +0 -66
- package/benchmark/auto-resolve/fixtures/F28-cli-rental-quote-rules/metadata.json +0 -10
- package/benchmark/auto-resolve/fixtures/F28-cli-rental-quote-rules/setup.sh +0 -23
- package/benchmark/auto-resolve/fixtures/F28-cli-rental-quote-rules/spec.md +0 -66
- package/benchmark/auto-resolve/fixtures/F28-cli-rental-quote-rules/task.txt +0 -11
- package/benchmark/auto-resolve/fixtures/F28-cli-rental-quote-rules/verifiers/exact-success.js +0 -44
- package/benchmark/auto-resolve/fixtures/F28-cli-rental-quote-rules/verifiers/rules-source.js +0 -58
- package/benchmark/auto-resolve/fixtures/F28-cli-rental-quote-rules/verifiers/unavailable-inventory.js +0 -35
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"verification_commands": [
|
|
3
|
-
{
|
|
4
|
-
"cmd": "node --test tests/cli.test.js",
|
|
5
|
-
"exit_code": 0,
|
|
6
|
-
"stdout_contains": [],
|
|
7
|
-
"stdout_not_contains": ["not ok "]
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/exact-success.js\"",
|
|
11
|
-
"exit_code": 0,
|
|
12
|
-
"stdout_contains": ["\"ok\":true"],
|
|
13
|
-
"stdout_not_contains": [],
|
|
14
|
-
"contract_refs": [
|
|
15
|
-
"Duplicate SKUs are combined before line totals are computed.",
|
|
16
|
-
"A successful redemption emits exact item rows, redemption rows, applied total, and amount due.",
|
|
17
|
-
"On success, write exactly one JSON object to stdout and no stderr. Keys: `order_id`, `subtotal_cents`, `gift_card_applied_cents`, `amount_due_cents`, `items`, `redemptions`."
|
|
18
|
-
]
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/insufficient-balance.js\"",
|
|
22
|
-
"exit_code": 0,
|
|
23
|
-
"stdout_contains": ["\"ok\":true"],
|
|
24
|
-
"stdout_not_contains": [],
|
|
25
|
-
"contract_refs": [
|
|
26
|
-
"Duplicate `card_id` redemption rows are combined before balance validation.",
|
|
27
|
-
"Combined card redemption over balance uses exact error shape `{ \"error\": \"insufficient_balance\", \"card_id\": string, \"available_cents\": number, \"requested_cents\": number }`.",
|
|
28
|
-
"The insufficient balance error object includes `error`, `card_id`, `available_cents`, and `requested_cents`."
|
|
29
|
-
]
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/rules-source.js\"",
|
|
33
|
-
"exit_code": 0,
|
|
34
|
-
"stdout_contains": ["\"ok\":true"],
|
|
35
|
-
"stdout_not_contains": [],
|
|
36
|
-
"contract_refs": [
|
|
37
|
-
"Product prices, gift-card balances, and gift-card active flags come from `data/gift-cards.json`. Do not hardcode product prices, card balances, or active flags in the command implementation.",
|
|
38
|
-
"Changing `data/gift-cards.json` product prices or card balances changes command output without code changes."
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
],
|
|
42
|
-
"forbidden_patterns": [
|
|
43
|
-
{
|
|
44
|
-
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{[^}]*return\\s+(null|undefined|''|\\{\\})",
|
|
45
|
-
"description": "silent catch returning fallback in gift-card path",
|
|
46
|
-
"files": ["bin/cli.js"],
|
|
47
|
-
"severity": "disqualifier"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}",
|
|
51
|
-
"description": "empty catch block",
|
|
52
|
-
"files": ["bin/cli.js"],
|
|
53
|
-
"severity": "disqualifier"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"pattern": "/\\*\\s*eslint-disable",
|
|
57
|
-
"description": "eslint-disable without scoped justification",
|
|
58
|
-
"files": ["bin/cli.js"],
|
|
59
|
-
"severity": "disqualifier"
|
|
60
|
-
}
|
|
61
|
-
],
|
|
62
|
-
"required_files": ["bin/cli.js", "tests/cli.test.js", "data/gift-cards.json"],
|
|
63
|
-
"forbidden_files": [],
|
|
64
|
-
"max_deps_added": 0,
|
|
65
|
-
"spec_output_files": ["bin/cli.js", "tests/cli.test.js"]
|
|
66
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "F27-cli-gift-card-redemption",
|
|
3
|
-
"category": "high-risk",
|
|
4
|
-
"difficulty": "high",
|
|
5
|
-
"timeout_seconds": 1500,
|
|
6
|
-
"required_tools": ["node"],
|
|
7
|
-
"browser": false,
|
|
8
|
-
"deps_change_expected": false,
|
|
9
|
-
"intent": "Add a bench-cli gift-card command that reads cart lines and gift-card redemption requests, prices products from data/gift-cards.json, combines duplicate SKUs and duplicate card redemptions before validation, and prints exact remaining balances and amount due in integer cents."
|
|
10
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# F27 setup - seed gift card product and balance rules.
|
|
3
|
-
set -e
|
|
4
|
-
|
|
5
|
-
mkdir -p data
|
|
6
|
-
|
|
7
|
-
cat > data/gift-cards.json <<'JSON'
|
|
8
|
-
{
|
|
9
|
-
"products": {
|
|
10
|
-
"TEE": { "unit_cents": 2500 },
|
|
11
|
-
"SOCKS": { "unit_cents": 700 },
|
|
12
|
-
"BAG": { "unit_cents": 3200 }
|
|
13
|
-
},
|
|
14
|
-
"cards": {
|
|
15
|
-
"GC-100": { "balance_cents": 5000, "active": true },
|
|
16
|
-
"GC-200": { "balance_cents": 2500, "active": true },
|
|
17
|
-
"GC-LOCKED": { "balance_cents": 9999, "active": false }
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
JSON
|
|
21
|
-
|
|
22
|
-
exit 0
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: "F27-cli-gift-card-redemption"
|
|
3
|
-
title: "Gift card command with redemption rules"
|
|
4
|
-
status: planned
|
|
5
|
-
complexity: high
|
|
6
|
-
depends-on: []
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# F27 Gift card command with redemption rules
|
|
10
|
-
|
|
11
|
-
## Context
|
|
12
|
-
|
|
13
|
-
`bench-cli` currently has greeting and version commands only. The task:
|
|
14
|
-
add a `gift-card` command that reads cart lines and gift-card redemption
|
|
15
|
-
requests from a JSON file, prices products from `data/gift-cards.json`, and
|
|
16
|
-
prints exact gift-card application totals in integer cents.
|
|
17
|
-
|
|
18
|
-
Gift-card balances are money-like state, so duplicate line and redemption
|
|
19
|
-
requests must be combined before validation. The command only calculates the
|
|
20
|
-
result; it does not persist balance changes.
|
|
21
|
-
|
|
22
|
-
## Requirements
|
|
23
|
-
|
|
24
|
-
- [ ] `bench-cli gift-card --input <path>` reads JSON shaped as `{ "order_id": string, "lines": [{ "sku": string, "qty": number }], "redeems": [{ "card_id": string, "amount_cents": number }] }`.
|
|
25
|
-
- [ ] Product prices, gift-card balances, and gift-card active flags come from `data/gift-cards.json`. Do not hardcode product prices, card balances, or active flags in the command implementation.
|
|
26
|
-
- [ ] Combine duplicate SKUs before computing line totals. The output `items` array must contain one row per SKU in first-seen order.
|
|
27
|
-
- [ ] Combine duplicate `card_id` redemption requests before validating balances and before computing remaining balances. The output `redemptions` array must contain one row per card in first-seen order.
|
|
28
|
-
- [ ] Validation happens before any result is printed. Invalid JSON, missing `lines`, missing `redeems`, unknown SKU, unknown card, inactive card, non-positive or non-integer `qty`, non-positive or non-integer `amount_cents`, missing `order_id`, combined card redemption over balance, or total redemption over subtotal exits `2` and writes exactly one JSON error object to stderr.
|
|
29
|
-
- [ ] Combined card redemption over balance uses exact error shape `{ "error": "insufficient_balance", "card_id": string, "available_cents": number, "requested_cents": number }`.
|
|
30
|
-
- [ ] Total redemption over subtotal uses exact error shape `{ "error": "redemption_exceeds_subtotal", "subtotal_cents": number, "requested_cents": number }`.
|
|
31
|
-
- [ ] On success, write exactly one JSON object to stdout and no stderr. Keys: `order_id`, `subtotal_cents`, `gift_card_applied_cents`, `amount_due_cents`, `items`, `redemptions`.
|
|
32
|
-
- [ ] Each output item row has keys `sku`, `qty`, `line_cents`. `line_cents` is `unit_cents * combined_qty`.
|
|
33
|
-
- [ ] Each redemption row has keys `card_id`, `applied_cents`, `remaining_balance_cents`. `remaining_balance_cents` is the starting balance from `data/gift-cards.json` minus the combined requested redemption for that card.
|
|
34
|
-
- [ ] `gift_card_applied_cents` is the sum of combined redemption amounts.
|
|
35
|
-
- [ ] `amount_due_cents = subtotal_cents - gift_card_applied_cents`.
|
|
36
|
-
- [ ] `tests/cli.test.js` is updated. Existing tests still pass AND at least two new tests cover `gift-card`: one successful redemption and one validation failure.
|
|
37
|
-
|
|
38
|
-
## Constraints
|
|
39
|
-
|
|
40
|
-
- **No new npm dependencies.**
|
|
41
|
-
- **No floating-money output.** All public amounts are integer cents.
|
|
42
|
-
- **No silent catches.** If parsing or file reading fails, emit a visible JSON error to stderr and exit `2`.
|
|
43
|
-
- **No extra stdout/stderr text** on the success path; downstream tooling parses stdout as JSON.
|
|
44
|
-
- **Lifecycle note.** The harness's DOCS phase flips this spec's frontmatter `status` after implementation completes — that is benchmark lifecycle bookkeeping, not a scope violation.
|
|
45
|
-
|
|
46
|
-
## Out of Scope
|
|
47
|
-
|
|
48
|
-
- Persisting changed gift-card balances.
|
|
49
|
-
- Taxes, shipping, coupons, or currencies beyond integer cents.
|
|
50
|
-
- Adding server routes or web UI.
|
|
51
|
-
- Touching `server/`, `web/`, or `tests/server.test.js`.
|
|
52
|
-
|
|
53
|
-
## Verification
|
|
54
|
-
|
|
55
|
-
- `node --test tests/cli.test.js` exits 0.
|
|
56
|
-
- Duplicate SKUs are combined before line totals are computed.
|
|
57
|
-
- Duplicate `card_id` redemption rows are combined before balance validation.
|
|
58
|
-
- A successful redemption emits exact item rows, redemption rows, applied total, and amount due.
|
|
59
|
-
- Changing `data/gift-cards.json` product prices or card balances changes command output without code changes.
|
|
60
|
-
- A combined card redemption over balance exits `2`, prints one JSON error to stderr, and prints no stdout.
|
|
61
|
-
- The insufficient balance error object includes `error`, `card_id`, `available_cents`, and `requested_cents`.
|
|
62
|
-
- `git diff --stat` shows only `bin/cli.js` and `tests/cli.test.js` touched (the gift-card seed comes from setup, not the arm).
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
Add a `gift-card` command to `bench-cli` so users can run `bench-cli gift-card --input <path>` with a JSON file shaped as `{ "order_id": string, "lines": [{ "sku": string, "qty": number }], "redeems": [{ "card_id": string, "amount_cents": number }] }`.
|
|
2
|
-
|
|
3
|
-
Read product prices, card balances, and active flags from `data/gift-cards.json`. Do not hardcode those values. Combine duplicate SKUs before computing line totals, and combine duplicate `card_id` redemption requests before validating balances or computing remaining balances.
|
|
4
|
-
|
|
5
|
-
On success, write one JSON object to stdout and no stderr with keys `order_id`, `subtotal_cents`, `gift_card_applied_cents`, `amount_due_cents`, `items`, and `redemptions`. Item rows have `sku`, `qty`, `line_cents`. Redemption rows have `card_id`, `applied_cents`, `remaining_balance_cents`. Preserve first-seen order for combined SKU rows and combined card rows.
|
|
6
|
-
|
|
7
|
-
Validation happens before any result is printed. Invalid JSON, missing `lines`, missing `redeems`, unknown SKU, unknown card, inactive card, non-positive or non-integer `qty`, non-positive or non-integer `amount_cents`, missing `order_id`, combined card redemption over balance, or total redemption over subtotal exits `2` and writes exactly one JSON error object to stderr. Combined card redemption over balance must use exact shape `{ "error": "insufficient_balance", "card_id": string, "available_cents": number, "requested_cents": number }`. Total redemption over subtotal must use exact shape `{ "error": "redemption_exceeds_subtotal", "subtotal_cents": number, "requested_cents": number }`.
|
|
8
|
-
|
|
9
|
-
Update `tests/cli.test.js` so existing tests still pass and at least two new tests cover `gift-card`: one successful redemption and one validation failure.
|
package/benchmark/auto-resolve/fixtures/F27-cli-gift-card-redemption/verifiers/exact-success.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
const assert = require('node:assert');
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
const os = require('node:os');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const { spawnSync } = require('node:child_process');
|
|
6
|
-
|
|
7
|
-
const workdir = process.env.BENCH_WORKDIR || process.cwd();
|
|
8
|
-
const input = path.join(os.tmpdir(), `gift-card-success-${process.pid}.json`);
|
|
9
|
-
|
|
10
|
-
fs.writeFileSync(input, JSON.stringify({
|
|
11
|
-
order_id: 'order-27',
|
|
12
|
-
lines: [
|
|
13
|
-
{ sku: 'TEE', qty: 1 },
|
|
14
|
-
{ sku: 'SOCKS', qty: 2 },
|
|
15
|
-
{ sku: 'TEE', qty: 2 },
|
|
16
|
-
{ sku: 'BAG', qty: 1 }
|
|
17
|
-
],
|
|
18
|
-
redeems: [
|
|
19
|
-
{ card_id: 'GC-100', amount_cents: 3000 },
|
|
20
|
-
{ card_id: 'GC-200', amount_cents: 1200 },
|
|
21
|
-
{ card_id: 'GC-100', amount_cents: 500 }
|
|
22
|
-
]
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
const proc = spawnSync('node', ['bin/cli.js', 'gift-card', '--input', input], {
|
|
26
|
-
cwd: workdir,
|
|
27
|
-
encoding: 'utf8'
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
assert.strictEqual(proc.status, 0, proc.stderr || proc.stdout);
|
|
31
|
-
assert.strictEqual(proc.stderr, '');
|
|
32
|
-
assert.deepStrictEqual(JSON.parse(proc.stdout), {
|
|
33
|
-
order_id: 'order-27',
|
|
34
|
-
subtotal_cents: 12100,
|
|
35
|
-
gift_card_applied_cents: 4700,
|
|
36
|
-
amount_due_cents: 7400,
|
|
37
|
-
items: [
|
|
38
|
-
{ sku: 'TEE', qty: 3, line_cents: 7500 },
|
|
39
|
-
{ sku: 'SOCKS', qty: 2, line_cents: 1400 },
|
|
40
|
-
{ sku: 'BAG', qty: 1, line_cents: 3200 }
|
|
41
|
-
],
|
|
42
|
-
redemptions: [
|
|
43
|
-
{ card_id: 'GC-100', applied_cents: 3500, remaining_balance_cents: 1500 },
|
|
44
|
-
{ card_id: 'GC-200', applied_cents: 1200, remaining_balance_cents: 1300 }
|
|
45
|
-
]
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
process.stdout.write(JSON.stringify({ ok: true }) + '\n');
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
const assert = require('node:assert');
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
const os = require('node:os');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const { spawnSync } = require('node:child_process');
|
|
6
|
-
|
|
7
|
-
const workdir = process.env.BENCH_WORKDIR || process.cwd();
|
|
8
|
-
const input = path.join(os.tmpdir(), `gift-card-balance-${process.pid}.json`);
|
|
9
|
-
|
|
10
|
-
fs.writeFileSync(input, JSON.stringify({
|
|
11
|
-
order_id: 'order-balance',
|
|
12
|
-
lines: [
|
|
13
|
-
{ sku: 'TEE', qty: 3 },
|
|
14
|
-
{ sku: 'BAG', qty: 1 }
|
|
15
|
-
],
|
|
16
|
-
redeems: [
|
|
17
|
-
{ card_id: 'GC-100', amount_cents: 3000 },
|
|
18
|
-
{ card_id: 'GC-100', amount_cents: 2500 }
|
|
19
|
-
]
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
const proc = spawnSync('node', ['bin/cli.js', 'gift-card', '--input', input], {
|
|
23
|
-
cwd: workdir,
|
|
24
|
-
encoding: 'utf8'
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
assert.strictEqual(proc.status, 2);
|
|
28
|
-
assert.strictEqual(proc.stdout, '');
|
|
29
|
-
assert.deepStrictEqual(JSON.parse(proc.stderr), {
|
|
30
|
-
error: 'insufficient_balance',
|
|
31
|
-
card_id: 'GC-100',
|
|
32
|
-
available_cents: 5000,
|
|
33
|
-
requested_cents: 5500
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
process.stdout.write(JSON.stringify({ ok: true }) + '\n');
|
package/benchmark/auto-resolve/fixtures/F27-cli-gift-card-redemption/verifiers/rules-source.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
const assert = require('node:assert');
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
const os = require('node:os');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const { spawnSync } = require('node:child_process');
|
|
6
|
-
|
|
7
|
-
const workdir = process.env.BENCH_WORKDIR || process.cwd();
|
|
8
|
-
const rulesPath = path.join(workdir, 'data', 'gift-cards.json');
|
|
9
|
-
const original = fs.readFileSync(rulesPath, 'utf8');
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
fs.writeFileSync(rulesPath, JSON.stringify({
|
|
13
|
-
products: {
|
|
14
|
-
TEE: { unit_cents: 1000 }
|
|
15
|
-
},
|
|
16
|
-
cards: {
|
|
17
|
-
'GC-SOURCE': { balance_cents: 900, active: true }
|
|
18
|
-
}
|
|
19
|
-
}, null, 2) + '\n');
|
|
20
|
-
|
|
21
|
-
const input = path.join(os.tmpdir(), `gift-card-source-${process.pid}.json`);
|
|
22
|
-
fs.writeFileSync(input, JSON.stringify({
|
|
23
|
-
order_id: 'order-source',
|
|
24
|
-
lines: [
|
|
25
|
-
{ sku: 'TEE', qty: 1 }
|
|
26
|
-
],
|
|
27
|
-
redeems: [
|
|
28
|
-
{ card_id: 'GC-SOURCE', amount_cents: 700 }
|
|
29
|
-
]
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
const proc = spawnSync('node', ['bin/cli.js', 'gift-card', '--input', input], {
|
|
33
|
-
cwd: workdir,
|
|
34
|
-
encoding: 'utf8'
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
assert.strictEqual(proc.status, 0, proc.stderr || proc.stdout);
|
|
38
|
-
assert.strictEqual(proc.stderr, '');
|
|
39
|
-
assert.deepStrictEqual(JSON.parse(proc.stdout), {
|
|
40
|
-
order_id: 'order-source',
|
|
41
|
-
subtotal_cents: 1000,
|
|
42
|
-
gift_card_applied_cents: 700,
|
|
43
|
-
amount_due_cents: 300,
|
|
44
|
-
items: [
|
|
45
|
-
{ sku: 'TEE', qty: 1, line_cents: 1000 }
|
|
46
|
-
],
|
|
47
|
-
redemptions: [
|
|
48
|
-
{ card_id: 'GC-SOURCE', applied_cents: 700, remaining_balance_cents: 200 }
|
|
49
|
-
]
|
|
50
|
-
});
|
|
51
|
-
} finally {
|
|
52
|
-
fs.writeFileSync(rulesPath, original);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
process.stdout.write(JSON.stringify({ ok: true }) + '\n');
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# F28 CLI rental quote rules
|
|
2
|
-
|
|
3
|
-
## Why this fixture exists
|
|
4
|
-
|
|
5
|
-
F27 was rejected because the direct bare arm passed every verifier. F28 returns
|
|
6
|
-
to the F16 pattern that produced valid lift: exact success shape plus exact
|
|
7
|
-
validation shape, with enough arithmetic and date handling that a direct
|
|
8
|
-
implementation is likely to leak extra fields or miss one contract.
|
|
9
|
-
|
|
10
|
-
## Pair expectation
|
|
11
|
-
|
|
12
|
-
PLAN must preserve the date-counting and duplicate-combine invariants.
|
|
13
|
-
IMPLEMENT must keep all public amounts in integer cents and read rules from
|
|
14
|
-
`data/rental-rules.json`. VERIFY should probe both the Friday-to-Tuesday
|
|
15
|
-
weekend count and the combined-stock exact error shape.
|
|
16
|
-
|
|
17
|
-
## Isolation
|
|
18
|
-
|
|
19
|
-
F16 covers checkout tax rules. F28 covers rental-day UTC math, weekend
|
|
20
|
-
surcharges, deposits, protection fees, and non-persistent inventory validation.
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"verification_commands": [
|
|
3
|
-
{
|
|
4
|
-
"cmd": "node --test tests/cli.test.js",
|
|
5
|
-
"exit_code": 0,
|
|
6
|
-
"stdout_contains": [],
|
|
7
|
-
"stdout_not_contains": ["not ok "]
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/exact-success.js\"",
|
|
11
|
-
"exit_code": 0,
|
|
12
|
-
"stdout_contains": ["\"ok\":true"],
|
|
13
|
-
"stdout_not_contains": [],
|
|
14
|
-
"contract_refs": [
|
|
15
|
-
"A Friday-to-Tuesday rental counts four rental days and two weekend days.",
|
|
16
|
-
"Duplicate SKUs are combined before stock validation and pricing.",
|
|
17
|
-
"A successful quote emits exact integer-cent weekend surcharge, discount, protection, deposit, and total fields."
|
|
18
|
-
]
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/unavailable-inventory.js\"",
|
|
22
|
-
"exit_code": 0,
|
|
23
|
-
"stdout_contains": ["\"ok\":true"],
|
|
24
|
-
"stdout_not_contains": [],
|
|
25
|
-
"contract_refs": [
|
|
26
|
-
"A combined quantity over stock exits `2`, prints one JSON error to stderr, and prints no stdout.",
|
|
27
|
-
"Combined quantity over stock uses exact error shape `{ \"error\": \"unavailable_inventory\", \"sku\": string, \"available\": number, \"requested\": number }`.",
|
|
28
|
-
"The unavailable inventory error object includes `error`, `sku`, `available`, and `requested`."
|
|
29
|
-
]
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"cmd": "node \"$BENCH_FIXTURE_DIR/verifiers/rules-source.js\"",
|
|
33
|
-
"exit_code": 0,
|
|
34
|
-
"stdout_contains": ["\"ok\":true"],
|
|
35
|
-
"stdout_not_contains": [],
|
|
36
|
-
"contract_refs": [
|
|
37
|
-
"Item daily rates, stock, deposits, weekend surcharge percent, protection daily price, and coupons come from `data/rental-rules.json`. Do not hardcode those values in the command implementation.",
|
|
38
|
-
"Changing `data/rental-rules.json` rates, deposits, or surcharge settings changes command output without code changes."
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
],
|
|
42
|
-
"forbidden_patterns": [
|
|
43
|
-
{
|
|
44
|
-
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{[^}]*return\\s+(null|undefined|''|\\{\\})",
|
|
45
|
-
"description": "silent catch returning fallback in rental quote path",
|
|
46
|
-
"files": ["bin/cli.js"],
|
|
47
|
-
"severity": "disqualifier"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"pattern": "catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}",
|
|
51
|
-
"description": "empty catch block",
|
|
52
|
-
"files": ["bin/cli.js"],
|
|
53
|
-
"severity": "disqualifier"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"pattern": "/\\*\\s*eslint-disable",
|
|
57
|
-
"description": "eslint-disable without scoped justification",
|
|
58
|
-
"files": ["bin/cli.js"],
|
|
59
|
-
"severity": "disqualifier"
|
|
60
|
-
}
|
|
61
|
-
],
|
|
62
|
-
"required_files": ["bin/cli.js", "tests/cli.test.js", "data/rental-rules.json"],
|
|
63
|
-
"forbidden_files": [],
|
|
64
|
-
"max_deps_added": 0,
|
|
65
|
-
"spec_output_files": ["bin/cli.js", "tests/cli.test.js"]
|
|
66
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "F28-cli-rental-quote-rules",
|
|
3
|
-
"category": "high-risk",
|
|
4
|
-
"difficulty": "high",
|
|
5
|
-
"timeout_seconds": 1500,
|
|
6
|
-
"required_tools": ["node"],
|
|
7
|
-
"browser": false,
|
|
8
|
-
"deps_change_expected": false,
|
|
9
|
-
"intent": "Add a bench-cli rental-quote command that prices equipment rentals from data/rental-rules.json using rental-day, weekend surcharge, discount, protection, deposit, and combined stock-validation rules."
|
|
10
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# F28 setup - seed rental quote rules.
|
|
3
|
-
set -e
|
|
4
|
-
|
|
5
|
-
mkdir -p data
|
|
6
|
-
|
|
7
|
-
cat > data/rental-rules.json <<'JSON'
|
|
8
|
-
{
|
|
9
|
-
"items": {
|
|
10
|
-
"CAM": { "daily_cents": 1200, "stock": 2, "deposit_cents": 5000 },
|
|
11
|
-
"LIGHT": { "daily_cents": 700, "stock": 3, "deposit_cents": 2000 },
|
|
12
|
-
"TRIPOD": { "daily_cents": 400, "stock": 5, "deposit_cents": 1000 }
|
|
13
|
-
},
|
|
14
|
-
"weekend_surcharge_percent": 25,
|
|
15
|
-
"protection_daily_cents": 300,
|
|
16
|
-
"coupons": {
|
|
17
|
-
"LONG3": { "percent": 10, "min_rental_days": 3 },
|
|
18
|
-
"NONE": { "percent": 0, "min_rental_days": 1 }
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
JSON
|
|
22
|
-
|
|
23
|
-
exit 0
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
id: "F28-cli-rental-quote-rules"
|
|
3
|
-
title: "Rental quote command with weekend and deposit rules"
|
|
4
|
-
status: planned
|
|
5
|
-
complexity: high
|
|
6
|
-
depends-on: []
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# F28 Rental quote command with weekend and deposit rules
|
|
10
|
-
|
|
11
|
-
## Context
|
|
12
|
-
|
|
13
|
-
`bench-cli` currently has greeting and version commands only. The task:
|
|
14
|
-
add a `rental-quote` command that reads an equipment rental request, prices it
|
|
15
|
-
from `data/rental-rules.json`, validates combined inventory, and prints one
|
|
16
|
-
exact integer-cents quote.
|
|
17
|
-
|
|
18
|
-
Rental quotes are operational contracts: duplicate item rows must be combined
|
|
19
|
-
before stock validation, weekend surcharge rules must be deterministic, and
|
|
20
|
-
success output must stay machine-readable without extra fields.
|
|
21
|
-
|
|
22
|
-
## Requirements
|
|
23
|
-
|
|
24
|
-
- [ ] `bench-cli rental-quote --input <path>` reads JSON shaped as `{ "start_date": "YYYY-MM-DD", "end_date": "YYYY-MM-DD", "coupon": string | null, "protection": boolean, "items": [{ "sku": string, "qty": number }] }`.
|
|
25
|
-
- [ ] Item daily rates, stock, deposits, weekend surcharge percent, protection daily price, and coupons come from `data/rental-rules.json`. Do not hardcode those values in the command implementation.
|
|
26
|
-
- [ ] `rental_days` is the number of calendar days from `start_date` inclusive to `end_date` exclusive, using UTC date math. `end_date` must be after `start_date`.
|
|
27
|
-
- [ ] A rental day is a weekend day when its UTC day is Saturday or Sunday.
|
|
28
|
-
- [ ] Combine duplicate SKUs before validating stock and before computing item rows. The output `items` array must contain one row per SKU in first-seen order.
|
|
29
|
-
- [ ] Validation happens before any quote is printed. Invalid JSON, missing `items`, unknown SKU, non-positive or non-integer `qty`, combined quantity over stock, invalid date format, `end_date` not after `start_date`, unknown coupon, or non-boolean `protection` exits `2` and writes exactly one JSON error object to stderr.
|
|
30
|
-
- [ ] Combined quantity over stock uses exact error shape `{ "error": "unavailable_inventory", "sku": string, "available": number, "requested": number }`.
|
|
31
|
-
- [ ] `subtotal_cents` is the sum of `daily_cents * combined_qty * rental_days` for all item rows.
|
|
32
|
-
- [ ] `weekend_surcharge_cents` is the sum of `Math.round(daily_cents * combined_qty * weekend_days * weekend_surcharge_percent / 100)` for all item rows.
|
|
33
|
-
- [ ] `discount_cents` is `Math.round((subtotal_cents + weekend_surcharge_cents) * coupon.percent / 100)` when a coupon is present and `rental_days >= coupon.min_rental_days`; otherwise `0`.
|
|
34
|
-
- [ ] `protection_cents` is `protection_daily_cents * total_combined_qty * rental_days` when `protection` is true; otherwise `0`.
|
|
35
|
-
- [ ] `deposit_cents` is the sum of `deposit_cents * combined_qty` for all item rows. Deposits are never discounted.
|
|
36
|
-
- [ ] `total_cents = subtotal_cents + weekend_surcharge_cents - discount_cents + protection_cents + deposit_cents`.
|
|
37
|
-
- [ ] On success, write exactly one JSON object to stdout and no stderr. Keys: `rental_days`, `weekend_days`, `subtotal_cents`, `weekend_surcharge_cents`, `discount_cents`, `protection_cents`, `deposit_cents`, `total_cents`, `items`.
|
|
38
|
-
- [ ] Each output item row has keys `sku`, `qty`, `rental_cents`, `deposit_cents`. `rental_cents` is `daily_cents * combined_qty * rental_days`, and row `deposit_cents` is the item's deposit times combined quantity.
|
|
39
|
-
- [ ] `tests/cli.test.js` is updated. Existing tests still pass AND at least two new tests cover `rental-quote`: one successful quote and one validation failure.
|
|
40
|
-
|
|
41
|
-
## Constraints
|
|
42
|
-
|
|
43
|
-
- **No new npm dependencies.**
|
|
44
|
-
- **No floating-money output.** All public amounts are integer cents.
|
|
45
|
-
- **No silent catches.** If parsing or file reading fails, emit a visible JSON error to stderr and exit `2`.
|
|
46
|
-
- **No extra stdout/stderr text** on the success path; downstream tooling parses stdout as JSON.
|
|
47
|
-
- **Lifecycle note.** The harness's DOCS phase flips this spec's frontmatter `status` after implementation completes — that is benchmark lifecycle bookkeeping, not a scope violation.
|
|
48
|
-
|
|
49
|
-
## Out of Scope
|
|
50
|
-
|
|
51
|
-
- Persisting reservations or mutating inventory.
|
|
52
|
-
- Hourly pricing, time zones beyond UTC date math, holidays, or blackout dates.
|
|
53
|
-
- Taxes, shipping, currencies, or payment capture.
|
|
54
|
-
- Adding server routes or web UI.
|
|
55
|
-
- Touching `server/`, `web/`, or `tests/server.test.js`.
|
|
56
|
-
|
|
57
|
-
## Verification
|
|
58
|
-
|
|
59
|
-
- `node --test tests/cli.test.js` exits 0.
|
|
60
|
-
- A Friday-to-Tuesday rental counts four rental days and two weekend days.
|
|
61
|
-
- Duplicate SKUs are combined before stock validation and pricing.
|
|
62
|
-
- A successful quote emits exact integer-cent weekend surcharge, discount, protection, deposit, and total fields.
|
|
63
|
-
- A combined quantity over stock exits `2`, prints one JSON error to stderr, and prints no stdout.
|
|
64
|
-
- The unavailable inventory error object includes `error`, `sku`, `available`, and `requested`.
|
|
65
|
-
- Changing `data/rental-rules.json` rates, deposits, or surcharge settings changes command output without code changes.
|
|
66
|
-
- `git diff --stat` shows only `bin/cli.js` and `tests/cli.test.js` touched (the rental rules seed comes from setup, not the arm).
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
Add a `rental-quote` command to `bench-cli` so users can run `bench-cli rental-quote --input <path>` with a JSON file shaped as `{ "start_date": "YYYY-MM-DD", "end_date": "YYYY-MM-DD", "coupon": string | null, "protection": boolean, "items": [{ "sku": string, "qty": number }] }`.
|
|
2
|
-
|
|
3
|
-
Read item daily rates, stock, deposits, weekend surcharge percent, protection daily price, and coupons from `data/rental-rules.json`. Do not hardcode those values. `rental_days` is the number of calendar days from `start_date` inclusive to `end_date` exclusive using UTC date math. A rental day is a weekend day when its UTC day is Saturday or Sunday.
|
|
4
|
-
|
|
5
|
-
Combine duplicate SKUs before stock validation and pricing. On success, write one JSON object to stdout and no stderr with keys `rental_days`, `weekend_days`, `subtotal_cents`, `weekend_surcharge_cents`, `discount_cents`, `protection_cents`, `deposit_cents`, `total_cents`, and `items`. Each item row has keys `sku`, `qty`, `rental_cents`, and `deposit_cents`.
|
|
6
|
-
|
|
7
|
-
Use these formulas: `subtotal_cents` is the sum of `daily_cents * combined_qty * rental_days`; `weekend_surcharge_cents` is the sum of `Math.round(daily_cents * combined_qty * weekend_days * weekend_surcharge_percent / 100)`; `discount_cents` is `Math.round((subtotal_cents + weekend_surcharge_cents) * coupon.percent / 100)` when a coupon is present and `rental_days >= coupon.min_rental_days`, otherwise `0`; `protection_cents` is `protection_daily_cents * total_combined_qty * rental_days` when `protection` is true, otherwise `0`; `deposit_cents` is the sum of item deposits times combined quantities; `total_cents = subtotal_cents + weekend_surcharge_cents - discount_cents + protection_cents + deposit_cents`.
|
|
8
|
-
|
|
9
|
-
Validation happens before any quote is printed. Invalid JSON, missing `items`, unknown SKU, non-positive or non-integer `qty`, combined quantity over stock, invalid date format, `end_date` not after `start_date`, unknown coupon, or non-boolean `protection` exits `2` and writes exactly one JSON error object to stderr. Combined quantity over stock must use exact shape `{ "error": "unavailable_inventory", "sku": string, "available": number, "requested": number }`.
|
|
10
|
-
|
|
11
|
-
Update `tests/cli.test.js` so existing tests still pass and at least two new tests cover `rental-quote`: one successful quote and one validation failure.
|
package/benchmark/auto-resolve/fixtures/F28-cli-rental-quote-rules/verifiers/exact-success.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
const assert = require('node:assert');
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
const os = require('node:os');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const { spawnSync } = require('node:child_process');
|
|
6
|
-
|
|
7
|
-
const workdir = process.env.BENCH_WORKDIR || process.cwd();
|
|
8
|
-
const input = path.join(os.tmpdir(), `rental-success-${process.pid}.json`);
|
|
9
|
-
|
|
10
|
-
fs.writeFileSync(input, JSON.stringify({
|
|
11
|
-
start_date: '2026-05-08',
|
|
12
|
-
end_date: '2026-05-12',
|
|
13
|
-
coupon: 'LONG3',
|
|
14
|
-
protection: true,
|
|
15
|
-
items: [
|
|
16
|
-
{ sku: 'CAM', qty: 1 },
|
|
17
|
-
{ sku: 'LIGHT', qty: 1 },
|
|
18
|
-
{ sku: 'CAM', qty: 1 }
|
|
19
|
-
]
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
const proc = spawnSync('node', ['bin/cli.js', 'rental-quote', '--input', input], {
|
|
23
|
-
cwd: workdir,
|
|
24
|
-
encoding: 'utf8'
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
assert.strictEqual(proc.status, 0, proc.stderr || proc.stdout);
|
|
28
|
-
assert.strictEqual(proc.stderr, '');
|
|
29
|
-
assert.deepStrictEqual(JSON.parse(proc.stdout), {
|
|
30
|
-
rental_days: 4,
|
|
31
|
-
weekend_days: 2,
|
|
32
|
-
subtotal_cents: 12400,
|
|
33
|
-
weekend_surcharge_cents: 1550,
|
|
34
|
-
discount_cents: 1395,
|
|
35
|
-
protection_cents: 3600,
|
|
36
|
-
deposit_cents: 12000,
|
|
37
|
-
total_cents: 28155,
|
|
38
|
-
items: [
|
|
39
|
-
{ sku: 'CAM', qty: 2, rental_cents: 9600, deposit_cents: 10000 },
|
|
40
|
-
{ sku: 'LIGHT', qty: 1, rental_cents: 2800, deposit_cents: 2000 }
|
|
41
|
-
]
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
process.stdout.write(JSON.stringify({ ok: true }) + '\n');
|
package/benchmark/auto-resolve/fixtures/F28-cli-rental-quote-rules/verifiers/rules-source.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
const assert = require('node:assert');
|
|
2
|
-
const fs = require('node:fs');
|
|
3
|
-
const os = require('node:os');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const { spawnSync } = require('node:child_process');
|
|
6
|
-
|
|
7
|
-
const workdir = process.env.BENCH_WORKDIR || process.cwd();
|
|
8
|
-
const rulesPath = path.join(workdir, 'data', 'rental-rules.json');
|
|
9
|
-
const original = fs.readFileSync(rulesPath, 'utf8');
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
fs.writeFileSync(rulesPath, JSON.stringify({
|
|
13
|
-
items: {
|
|
14
|
-
CAM: { daily_cents: 1000, stock: 3, deposit_cents: 3000 }
|
|
15
|
-
},
|
|
16
|
-
weekend_surcharge_percent: 50,
|
|
17
|
-
protection_daily_cents: 100,
|
|
18
|
-
coupons: {
|
|
19
|
-
HALF: { percent: 50, min_rental_days: 1 }
|
|
20
|
-
}
|
|
21
|
-
}, null, 2) + '\n');
|
|
22
|
-
|
|
23
|
-
const input = path.join(os.tmpdir(), `rental-source-${process.pid}.json`);
|
|
24
|
-
fs.writeFileSync(input, JSON.stringify({
|
|
25
|
-
start_date: '2026-05-09',
|
|
26
|
-
end_date: '2026-05-11',
|
|
27
|
-
coupon: 'HALF',
|
|
28
|
-
protection: true,
|
|
29
|
-
items: [
|
|
30
|
-
{ sku: 'CAM', qty: 1 }
|
|
31
|
-
]
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
const proc = spawnSync('node', ['bin/cli.js', 'rental-quote', '--input', input], {
|
|
35
|
-
cwd: workdir,
|
|
36
|
-
encoding: 'utf8'
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
assert.strictEqual(proc.status, 0, proc.stderr || proc.stdout);
|
|
40
|
-
assert.strictEqual(proc.stderr, '');
|
|
41
|
-
assert.deepStrictEqual(JSON.parse(proc.stdout), {
|
|
42
|
-
rental_days: 2,
|
|
43
|
-
weekend_days: 2,
|
|
44
|
-
subtotal_cents: 2000,
|
|
45
|
-
weekend_surcharge_cents: 1000,
|
|
46
|
-
discount_cents: 1500,
|
|
47
|
-
protection_cents: 200,
|
|
48
|
-
deposit_cents: 3000,
|
|
49
|
-
total_cents: 4700,
|
|
50
|
-
items: [
|
|
51
|
-
{ sku: 'CAM', qty: 1, rental_cents: 2000, deposit_cents: 3000 }
|
|
52
|
-
]
|
|
53
|
-
});
|
|
54
|
-
} finally {
|
|
55
|
-
fs.writeFileSync(rulesPath, original);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
process.stdout.write(JSON.stringify({ ok: true }) + '\n');
|