@navels/neal 0.2.0 → 0.3.0

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.
Files changed (114) hide show
  1. package/README.md +206 -209
  2. package/SECURITY.md +16 -19
  3. package/dist/neal/adjudicator/{blocked-adjudicator.js → consultant.js} +21 -21
  4. package/dist/neal/adjudicator/execute.js +33 -33
  5. package/dist/neal/adjudicator/final-completion.js +2 -31
  6. package/dist/neal/adjudicator/planning.js +230 -38
  7. package/dist/neal/agents/prompts.js +6 -17
  8. package/dist/neal/agents/rounds.js +43 -73
  9. package/dist/neal/agents/schemas.js +47 -13
  10. package/dist/neal/blocked-guidance.js +16 -9
  11. package/dist/neal/cli.js +2 -2
  12. package/dist/neal/commands/check.js +2 -2
  13. package/dist/neal/commands/compat.js +100 -36
  14. package/dist/neal/commands/interactive-activity.js +2 -2
  15. package/dist/neal/commands/recovery-guidance.js +20 -8
  16. package/dist/neal/commands/runtime.js +4 -2
  17. package/dist/neal/config.js +16 -7
  18. package/dist/neal/context/inline-review-context.js +13 -28
  19. package/dist/neal/context/reviewer-context.js +19 -14
  20. package/dist/neal/eval/reviewer-eval-manifest.js +86 -0
  21. package/dist/neal/eval/reviewer-eval.js +155 -0
  22. package/dist/neal/interactive-controls.js +5 -0
  23. package/dist/neal/orchestrator/completion.js +49 -2
  24. package/dist/neal/orchestrator/failures.js +7 -0
  25. package/dist/neal/orchestrator/notifications.js +9 -9
  26. package/dist/neal/orchestrator/phases/planning.js +117 -80
  27. package/dist/neal/orchestrator/phases/recovery.js +151 -115
  28. package/dist/neal/orchestrator/phases/review.js +28 -1
  29. package/dist/neal/orchestrator/transitions.js +7 -1
  30. package/dist/neal/orchestrator.js +3 -0
  31. package/dist/neal/plan-queue.js +29 -1
  32. package/dist/neal/prompts/execute.js +7 -19
  33. package/dist/neal/prompts/planning.js +64 -55
  34. package/dist/neal/prompts/review-doctrine.js +13 -50
  35. package/dist/neal/prompts/specialized.js +7 -17
  36. package/dist/neal/prompts/specs.js +180 -32
  37. package/dist/neal/providers/agent-settings-isolation.js +35 -0
  38. package/dist/neal/providers/anthropic-claude.js +18 -1
  39. package/dist/neal/providers/detection.js +3 -7
  40. package/dist/neal/providers/git-config-isolation.js +83 -0
  41. package/dist/neal/providers/openai-codex.js +25 -3
  42. package/dist/neal/providers/{generic-agentic-tools.js → openai-compatible-tools.js} +11 -5
  43. package/dist/neal/providers/openai-compatible.js +1078 -430
  44. package/dist/neal/providers/openrouter-routing.js +68 -0
  45. package/dist/neal/providers/pricing.js +1 -1
  46. package/dist/neal/providers/rate-card.js +6 -32
  47. package/dist/neal/providers/registry.js +6 -17
  48. package/dist/neal/providers/types.js +12 -0
  49. package/dist/neal/resume-decision.js +7 -6
  50. package/dist/neal/resume-planner.js +9 -0
  51. package/dist/neal/retrospective.js +3 -3
  52. package/dist/neal/review-debt.js +30 -0
  53. package/dist/neal/review-findings/provider.js +17 -29
  54. package/dist/neal/review.js +17 -1
  55. package/dist/neal/state-invariants.js +11 -3
  56. package/dist/neal/state-views.js +27 -5
  57. package/dist/neal/state.js +59 -15
  58. package/dist/neal/status.js +28 -1
  59. package/dist/neal/support.js +3 -3
  60. package/docs/README.md +22 -18
  61. package/docs/{ADJUDICATOR_INVENTORY.md → adjudicator-inventory.md} +58 -57
  62. package/docs/architecture.md +79 -83
  63. package/docs/automation.md +11 -11
  64. package/docs/compat.md +91 -61
  65. package/docs/compatible-models.md +145 -121
  66. package/docs/demo.md +15 -5
  67. package/docs/maintenance.md +65 -34
  68. package/docs/plan-format.md +61 -33
  69. package/docs/prompt-evals.md +96 -0
  70. package/docs/{PROMPT_SPECS.md → prompt-specs.md} +52 -48
  71. package/docs/providers.md +279 -349
  72. package/docs/release.md +18 -19
  73. package/docs/review-convergence.md +246 -0
  74. package/docs/state-machine.md +61 -59
  75. package/docs/storage.md +40 -31
  76. package/docs/troubleshooting.md +32 -29
  77. package/examples/compat/add-edit-verify/PLAN.md +2 -0
  78. package/examples/compat/add-edit-verify/broken.diff +2 -2
  79. package/examples/compat/add-edit-verify/good.diff +2 -2
  80. package/examples/compat/add-edit-verify/src/add.js +1 -1
  81. package/examples/compat/manifest.json +25 -29
  82. package/examples/compat/sum-grep-edit/PLAN.md +33 -0
  83. package/examples/compat/sum-grep-edit/broken.diff +12 -0
  84. package/examples/compat/sum-grep-edit/good.diff +12 -0
  85. package/examples/compat/sum-grep-edit/package.json +5 -0
  86. package/examples/compat/sum-grep-edit/src/numbers.js +9 -0
  87. package/examples/compat/sum-grep-edit/test/numbers.test.js +15 -0
  88. package/examples/issue-triage-js/README.md +0 -15
  89. package/examples/reviewer-eval/clean-doc/base/src/clamp.ts +3 -0
  90. package/examples/reviewer-eval/clean-doc/change.diff +9 -0
  91. package/examples/reviewer-eval/clean-extract/base/src/greet.ts +3 -0
  92. package/examples/reviewer-eval/clean-extract/change.diff +13 -0
  93. package/examples/reviewer-eval/dropped-error/base/src/load.ts +4 -0
  94. package/examples/reviewer-eval/dropped-error/change.diff +15 -0
  95. package/examples/reviewer-eval/manifest.json +45 -0
  96. package/examples/reviewer-eval/off-by-one/base/src/paginate.ts +5 -0
  97. package/examples/reviewer-eval/off-by-one/change.diff +11 -0
  98. package/{config.yml → neal.yml} +1 -1
  99. package/package.json +15 -14
  100. package/dist/neal/providers/generic-agentic.js +0 -1256
  101. package/docs/comparison.md +0 -105
  102. package/docs/issue-pipeline.md +0 -124
  103. package/examples/compat/is-even-add-test/PLAN.md +0 -30
  104. package/examples/compat/is-even-add-test/broken.diff +0 -11
  105. package/examples/compat/is-even-add-test/good.diff +0 -11
  106. package/examples/compat/is-even-add-test/package.json +0 -5
  107. package/examples/compat/is-even-add-test/src/is-even.js +0 -3
  108. package/examples/compat/is-even-add-test/test/is-even.test.js +0 -9
  109. package/examples/compat/reverse-grep-edit/PLAN.md +0 -32
  110. package/examples/compat/reverse-grep-edit/broken.diff +0 -12
  111. package/examples/compat/reverse-grep-edit/good.diff +0 -12
  112. package/examples/compat/reverse-grep-edit/package.json +0 -5
  113. package/examples/compat/reverse-grep-edit/src/strings.js +0 -10
  114. package/examples/compat/reverse-grep-edit/test/strings.test.js +0 -15
@@ -1,105 +0,0 @@
1
- # Why neal instead of X?
2
-
3
- neal is a harness, not an agent. It runs three independent roles — planner,
4
- coder, reviewer — each bound to whatever provider and model you choose
5
- ([providers.md](providers.md)), and drives them with a deterministic state
6
- machine ([architecture.md](architecture.md)) rather than letting a model decide
7
- the control flow. Verdicts are schema-validated structured output, not prose an
8
- orchestrator model interprets. Every run is persisted under `.neal/` and
9
- resumable after a crash ([storage.md](storage.md)). The reviewer is read-only by
10
- construction: the provider registry rejects any reviewer capability that
11
- declares write or shell access, so a reviewer structurally cannot mutate the
12
- repo regardless of prompt. Most alternatives are agents; neal is the loop that
13
- holds agents accountable to a plan.
14
-
15
- ## Claude Code
16
-
17
- What it's great at: interactive, conversational coding with a single very
18
- capable agent. Its subagents delegate work, plan mode gates execution behind an
19
- approved plan, and hooks let you inject your own checks into the tool loop.
20
-
21
- The structural difference: Claude Code is a single-vendor agent product.
22
- Its subagents are prompt-scoped delegates inside one product, sharing a vendor
23
- and orchestrated by a model — the top-level agent decides when to delegate and
24
- how to weigh what comes back. There is no cross-vendor adversarial review: the
25
- judge and the judged are the same vendor, often the same model family. neal is
26
- not a competitor to Claude Code; it is a consumer of it. The `anthropic-claude`
27
- adapter drives Claude Code's SDK as a coder or reviewer, and the common
28
- configuration is exactly the adversarial split Claude Code cannot express:
29
- Codex codes, Claude reviews, and a deterministic orchestrator — not a model —
30
- decides what happens with the verdict.
31
-
32
- Pick Claude Code when the work is exploratory or conversational, when you want
33
- a human in the loop turn by turn, or when you have no plan yet. neal wants a
34
- plan document; Claude Code is a good place to figure one out.
35
-
36
- ## Aider (architect/editor mode)
37
-
38
- What it's great at: fast, terminal-native pair programming. Its
39
- architect/editor mode was an early demonstration that splitting reasoning from
40
- editing across two models improves results.
41
-
42
- The structural difference: Aider's two models cooperate on one change — the
43
- architect proposes, the editor applies — and the control flow between them is
44
- mediated by the models within a chat session. There is no independent reviewer
45
- producing a structured verdict that a state machine acts on, no scoped plan
46
- queue where each scope starts the coder from a fresh context, and continuity is
47
- conversation-shaped rather than a persisted run ledger. neal's roles are
48
- adversarial rather than cooperative: the reviewer judges committed diffs it had
49
- no hand in producing, findings route back to the coder until a schema-validated
50
- accept, and `neal resume` reconstructs an interrupted run from the `.neal/`
51
- ledgers rather than replaying a chat.
52
-
53
- Pick Aider for tight interactive edits where you are the reviewer and the loop
54
- is you. neal earns its overhead on multi-scope work where drift over a long
55
- session is the failure mode.
56
-
57
- ## OpenHands and autonomous agents
58
-
59
- What they're great at: sandboxed, hands-off execution — hand an issue to an
60
- agent in an isolated runtime and come back to a pull request. The sandbox makes
61
- broad autonomy defensible.
62
-
63
- The structural difference: these systems bet on maximizing agent autonomy and
64
- containing it with a sandbox. neal bets the opposite way: local-first execution
65
- in your checkout, a human-authored plan as the contract for what may happen,
66
- and bounded autonomy with structural escalation — blocked states that wait for
67
- `neal resume --message`, manual gates for work only a human can do, and a
68
- bounded adjudicator that resolves a narrow recoverable class and escalates
69
- genuine walls. Even `--unattended` mode only bounds how long neal persists
70
- before failing cleanly; it never widens authorization.
71
-
72
- Pick an autonomous agent when you want isolation and don't need to constrain
73
- the path taken. Pick neal when the plan is the point — when you want to
74
- specify the decomposition, the verification per scope, and the stop conditions,
75
- and have the machine enforce them.
76
-
77
- ## If you just want CI review
78
-
79
- PR-review bots comment on diffs after they reach the forge. neal's review is
80
- inside the loop: every scope's committed diff is judged before the run moves
81
- on, and a final-completion review checks the whole plan before the squash — so
82
- findings are fixed by the same coder in the same run, not triaged later.
83
- `neal review` also works standalone against any committed range, role-symmetric
84
- (the coder provider drafts findings, the read-only reviewer adjudicates them),
85
- local, and provider-agnostic — no forge integration or CI required. The two are
86
- complementary: neal reviews before you push, bots review after.
87
-
88
- ## What neal does not do
89
-
90
- - **No GUI.** neal is a CLI with narrative terminal output; state lives in
91
- files under `.neal/`.
92
- - **No sandbox of its own for the coder.** Writer providers run with broad
93
- local permissions (Codex under `danger-full-access`, Claude under
94
- `bypassPermissions`, the generic writer's `run` tool is unsandboxed shell).
95
- Bring your own isolation — disposable checkout, container, or VM. See
96
- [SECURITY.md](../SECURITY.md).
97
- - **Serial scopes only, today.** Scopes execute one at a time, and `neal run`
98
- queues run plans serially. There is no parallel scope execution.
99
- - **It requires a plan document.** There is no "just fix it" mode — the
100
- authored plan, with its mandatory `## Execution Shape` section
101
- ([plan-format.md](plan-format.md)), is the product's opinion. If writing a
102
- plan feels like overhead, neal is the wrong tool for that task.
103
- - **No provider plugins yet.** Adapters are built-in (`openai-codex`,
104
- `anthropic-claude`, `openai-compatible`, `generic-agentic`); external
105
- provider plugins are future work.
@@ -1,124 +0,0 @@
1
- # GitHub Issue Pipeline
2
-
3
- Assign a GitHub issue to neal and get a reviewed pull request back. A maintainer
4
- applies a trigger label to an issue; a GitHub Actions runner turns the issue
5
- into a neal plan, runs neal's planner/coder/reviewer loop to completion, and
6
- opens a pull request assigned to a human — or reports back on the issue if neal
7
- blocks. Humans own merge; the pipeline never pushes to a protected branch.
8
-
9
- This is a self-hosting use of neal itself: neal already *is* the middle of the
10
- loop (cross-model review, deterministic gating, bounded recovery), so the
11
- pipeline is a thin trigger plus a thin publish around `neal run`.
12
-
13
- ## What it does
14
-
15
- ```
16
- maintainer applies the trigger label to an issue
17
- │ (gate: the labeler must have write access — the issue author is irrelevant)
18
-
19
- issue title + body ──▶ seed .neal/PLAN.md ──▶ neal run --unattended
20
- │ (Opus 4.8 writes, sol reviews each diff)
21
- ├─ exit 0 ──▶ push neal/issue-<n>, open a PR, assign the maintainer
22
- ├─ exit 2 ──▶ comment the blocker on the issue, keep the branch
23
- └─ exit 3 ──▶ comment the failure on the issue, open no PR
24
- ```
25
-
26
- The plan is seeded inside `.neal/` — wrapper-owned, gitignored territory — so
27
- it can never collide with a tracked file or appear in review diffs, and it is
28
- never committed. The refined plan travels with the outcome instead: the PR
29
- body (or the blocked/failed issue comment) embeds it in a collapsed details
30
- block, which outlives the 3-day `.neal/` artifact retention.
31
-
32
- The outcome also carries a cost ledger — per-provider turns, tokens, and
33
- dollars aggregated from the run's `RUN_METRICS-final*.json` files. Claude
34
- cost is provider-reported; reviewer cost is rate-computed from neal's
35
- vendored published rate card (see `docs/providers.md`) and marked as such.
36
-
37
- ## Running an issue locally
38
-
39
- `scripts/neal-issue-local.sh <issue-number> [plan-doc]` is the pipeline's
40
- local twin for subscription billing: the coder rides the Claude CLI login and
41
- the reviewer the codex CLI login (per `~/.neal/config.yml`) instead of the
42
- API keys CI uses. It shares the plan seed and PR-body construction with
43
- `pipeline/run.sh` through `pipeline/lib.sh`, so a local run produces the same
44
- branch name, the same PR format, and the same embedded plan and cost ledger.
45
- The optional second argument substitutes a pre-authored plan (for example a
46
- `multi_scope` plan spanning several issues) for the seeded `one_shot` one.
47
- The script unsets `OPENAI_API_KEY`/`ANTHROPIC_API_KEY` for the run so a stray
48
- exported key cannot silently switch billing from subscription to API.
49
-
50
- Every run executes in its own git worktree under `<repo>-worktrees/issue-<n>`,
51
- branched from `origin/main`. Runs on different issues can proceed in parallel
52
- (git enforces one worktree per branch), and the main checkout stays free for
53
- your own work throughout. On success the worktree is removed after the push —
54
- the branch lives on the PR. On blocked/failed outcomes the worktree is kept:
55
- it is the resume state (`cd` in and `neal resume`). The practical concurrency
56
- cap is your subscriptions, not git: parallel runs share your Claude and
57
- ChatGPT rate limits.
58
-
59
- - **Coder:** Claude Opus 4.8 (`anthropic-claude` adapter, `ANTHROPIC_API_KEY`).
60
- - **Reviewer:** GPT-5.6-sol via the `openai-compatible` adapter pointed at the
61
- OpenAI API (`OPENAI_API_KEY`). The reviewer judges neal-inlined diff/context;
62
- it has no repository tools, which keeps the runner setup to two env vars and
63
- no extra CLI.
64
-
65
- ## Setup
66
-
67
- One-time, by a maintainer. The workflow no-ops safely until this is done.
68
-
69
- 1. **Secrets** (repo → Settings → Secrets → Actions, on the `neal-pipeline`
70
- environment): `ANTHROPIC_API_KEY` and `OPENAI_API_KEY`. Use short-lived,
71
- minimally-scoped keys and rotate them (see Security below).
72
- 2. **Environment**: create a `neal-pipeline` environment (Settings →
73
- Environments). Optionally add required reviewers so each run needs a manual
74
- click before the secrets are exposed — recommended for a public repo.
75
- 3. **Reviewer model**: set the repo variable `NEAL_REVIEWER_MODEL` to the sol
76
- model slug your OpenAI key serves. Before the first run, qualify it:
77
- `neal compat --model <slug> --role reviewer --reference anthropic-claude`
78
- (locally, with the same key). If sol is not reachable over the OpenAI
79
- Chat Completions API, point `NEAL_REVIEWER_BASE_URL`/`NEAL_REVIEWER_MODEL` at
80
- an endpoint that serves it, or switch the reviewer to the `openai-codex`
81
- adapter in `pipeline/config.yml`.
82
- 4. **Trigger label**: create a label (default `neal:go`; override with the repo
83
- variable `NEAL_TRIGGER_LABEL`). Optionally set `NEAL_READY_LABEL` (e.g.
84
- `ready-for-human`) to tag the PRs neal opens.
85
- 5. **Branch protection**: protect `main` (require PR + review). The pipeline can
86
- push `neal/issue-*` branches and open PRs, but must never be able to merge or
87
- push to `main`.
88
-
89
- ## Security model
90
-
91
- This runs on a public repository, so the trust boundary is "arbitrary internet
92
- text becomes instructions to an agent with push access." The controls:
93
-
94
- - **Maintainer-gated trigger.** The gate checks the *labeler's* repo permission
95
- (`write`/`maintain`/`admin`), not the issue author. An untrusted issue cannot
96
- start a run — only a maintainer applying the label can.
97
- - **Untrusted issue input.** The issue title/body are passed as environment
98
- variables (never interpolated into a shell command) and written to the seed
99
- plan with `printf '%s'`, so they cannot inject shell commands. neal then
100
- treats the seed as plan input, refined through the planner/reviewer loop.
101
- - **Least privilege.** The workflow token can push `neal/issue-*` branches, open
102
- PRs, and comment — nothing else. Branch protection keeps the human merge gate.
103
- - **Boundaries in the plan.** The seed forbids edits to `.github/**` /
104
- `pipeline/**` and forbids reading or printing secrets — defense-in-depth on
105
- top of the token scope.
106
- - **Secret exposure caveat.** The coder runs with shell in the runner, where
107
- `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` live in the environment. A
108
- prompt-injected coder could try to exfiltrate them; the maintainer gate is
109
- what makes that acceptable (only a maintainer's labeled issue ever runs). Use
110
- short-lived, minimally-scoped keys, the `neal-pipeline` environment's approval
111
- gate, and rotate on any suspicion.
112
-
113
- ## v1 limitations
114
-
115
- - **Seed shape is `one_shot`.** Best for well-scoped issues ("fix X", "add Y").
116
- For large multi-part work neal's coder will split the scope into a sub-plan;
117
- if it can't be done in scope, neal blocks and comments asking for a smaller
118
- issue. Write issues with a clear, bounded objective for best results.
119
- - **Reviewer reads inlined context, not the repo.** The `openai-compatible`
120
- reviewer judges the diff neal inlines, not the working tree. Switch to an
121
- `openai-codex` reviewer (OS-sandboxed, repo-reading) once sol is confirmed on
122
- that adapter's API path, if repo-reading review is wanted.
123
- - **No comment-driven iteration yet.** On a blocked run you re-apply the label to
124
- retry; a maintainer-comment → `neal resume --message` loop is a v2 addition.
@@ -1,30 +0,0 @@
1
- # Compat fixture: replace the placeholder test
2
-
3
- ## Execution Shape
4
-
5
- executionShape: one_shot
6
-
7
- ## Objective
8
-
9
- `src/is-even.js` exports a correct `isEven(n)` helper. Its test in
10
- `test/is-even.test.js` is only a placeholder that always fails
11
- (`assert.ok(false, ...)`).
12
-
13
- Replace the placeholder assertion with a real test that exercises `isEven` —
14
- asserting `isEven(4)` is `true` is enough — so the test suite passes. Keep the
15
- existing `test(...)` block; only the placeholder assertion needs to change.
16
-
17
- ## Boundaries
18
-
19
- Allowed paths:
20
-
21
- - `test/is-even.test.js`
22
-
23
- Forbidden:
24
-
25
- - Do not edit `src/is-even.js` (it is already correct).
26
- - Do not add dependencies or new files.
27
-
28
- ## Verification
29
-
30
- Run `node --test test/is-even.test.js`; it must exit `0`.
@@ -1,11 +0,0 @@
1
- diff --git a/test/is-even.test.js b/test/is-even.test.js
2
- index 52c2a94..0e9eec5 100644
3
- --- a/test/is-even.test.js
4
- +++ b/test/is-even.test.js
5
- @@ -5,5 +5,5 @@ import { isEven } from '../src/is-even.js';
6
-
7
- test('isEven identifies even numbers', () => {
8
- // TODO: replace this placeholder with a real assertion on isEven.
9
- - assert.ok(false, 'TODO: replace this placeholder with a real test for isEven');
10
- + assert.equal(isEven(4), false);
11
- });
@@ -1,11 +0,0 @@
1
- diff --git a/test/is-even.test.js b/test/is-even.test.js
2
- index 52c2a94..28d1d73 100644
3
- --- a/test/is-even.test.js
4
- +++ b/test/is-even.test.js
5
- @@ -5,5 +5,5 @@ import { isEven } from '../src/is-even.js';
6
-
7
- test('isEven identifies even numbers', () => {
8
- // TODO: replace this placeholder with a real assertion on isEven.
9
- - assert.ok(false, 'TODO: replace this placeholder with a real test for isEven');
10
- + assert.equal(isEven(4), true);
11
- });
@@ -1,5 +0,0 @@
1
- {
2
- "name": "@navels/neal-compat-is-even-add-test",
3
- "private": true,
4
- "type": "module"
5
- }
@@ -1,3 +0,0 @@
1
- // Trivial compat fixture: this helper is already correct. The work is to add a
2
- // real test in place of the placeholder.
3
- export const isEven = (n) => n % 2 === 0;
@@ -1,9 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
-
4
- import { isEven } from '../src/is-even.js';
5
-
6
- test('isEven identifies even numbers', () => {
7
- // TODO: replace this placeholder with a real assertion on isEven.
8
- assert.ok(false, 'TODO: replace this placeholder with a real test for isEven');
9
- });
@@ -1,32 +0,0 @@
1
- # Compat fixture: fix the broken string helper
2
-
3
- ## Execution Shape
4
-
5
- executionShape: one_shot
6
-
7
- ## Objective
8
-
9
- `src/strings.js` exports several small string helpers. Exactly one of them is
10
- buggy: `reverse(s)` is supposed to return the string with its characters
11
- reversed, but it returns the string unchanged. The `reverse` test in
12
- `test/strings.test.js` fails because of this.
13
-
14
- Read `src/strings.js`, locate the faulty `reverse` helper, and make the smallest
15
- complete change so it returns the reversed string. Do not touch the other
16
- helpers, which already pass their tests.
17
-
18
- ## Boundaries
19
-
20
- Allowed paths:
21
-
22
- - `src/strings.js`
23
-
24
- Forbidden:
25
-
26
- - Do not edit the test file.
27
- - Do not change the other (already-correct) helpers.
28
- - Do not add dependencies or new files.
29
-
30
- ## Verification
31
-
32
- Run `node --test test/strings.test.js`; it must exit `0`.
@@ -1,12 +0,0 @@
1
- diff --git a/src/strings.js b/src/strings.js
2
- index c338691..fbfe71b 100644
3
- --- a/src/strings.js
4
- +++ b/src/strings.js
5
- @@ -5,6 +5,6 @@ export const upper = (s) => s.toUpperCase();
6
- export const lower = (s) => s.toLowerCase();
7
-
8
- // BUG: this does not actually reverse the string.
9
- -export const reverse = (s) => s.split('').join('');
10
- +export const reverse = (s) => s.split('').join('-');
11
-
12
- export const repeat = (s, n) => s.repeat(n);
@@ -1,12 +0,0 @@
1
- diff --git a/src/strings.js b/src/strings.js
2
- index c338691..d17fc16 100644
3
- --- a/src/strings.js
4
- +++ b/src/strings.js
5
- @@ -5,6 +5,6 @@ export const upper = (s) => s.toUpperCase();
6
- export const lower = (s) => s.toLowerCase();
7
-
8
- // BUG: this does not actually reverse the string.
9
- -export const reverse = (s) => s.split('').join('');
10
- +export const reverse = (s) => s.split('').reverse().join('');
11
-
12
- export const repeat = (s, n) => s.repeat(n);
@@ -1,5 +0,0 @@
1
- {
2
- "name": "@navels/neal-compat-reverse-grep-edit",
3
- "private": true,
4
- "type": "module"
5
- }
@@ -1,10 +0,0 @@
1
- // Trivial compat fixture: several string helpers live here. Exactly one of them
2
- // is buggy, so an agent must read/grep to locate the faulty function.
3
- export const upper = (s) => s.toUpperCase();
4
-
5
- export const lower = (s) => s.toLowerCase();
6
-
7
- // BUG: this does not actually reverse the string.
8
- export const reverse = (s) => s.split('').join('');
9
-
10
- export const repeat = (s, n) => s.repeat(n);
@@ -1,15 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import test from 'node:test';
3
-
4
- import { lower, repeat, reverse, upper } from '../src/strings.js';
5
-
6
- test('upper/lower/repeat helpers are correct', () => {
7
- assert.equal(upper('abc'), 'ABC');
8
- assert.equal(lower('ABC'), 'abc');
9
- assert.equal(repeat('ab', 3), 'ababab');
10
- });
11
-
12
- test('reverse returns the string reversed', () => {
13
- assert.equal(reverse('abc'), 'cba');
14
- assert.equal(reverse('neal'), 'laen');
15
- });