human-to-code 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +90 -62
- package/assets/banner.svg +9 -14
- package/assets/brand/social-card.svg +6 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +79 -74
- package/dist/cli.js.map +1 -1
- package/dist/pipeline/agent-tools.d.ts +348 -0
- package/dist/pipeline/agent-tools.d.ts.map +1 -0
- package/dist/pipeline/agent-tools.js +542 -0
- package/dist/pipeline/agent-tools.js.map +1 -0
- package/dist/pipeline/agent-tui.d.ts +15 -0
- package/dist/pipeline/agent-tui.d.ts.map +1 -0
- package/dist/pipeline/agent-tui.js +133 -0
- package/dist/pipeline/agent-tui.js.map +1 -0
- package/dist/pipeline/deep-agent.d.ts +22 -203
- package/dist/pipeline/deep-agent.d.ts.map +1 -1
- package/dist/pipeline/deep-agent.js +320 -190
- package/dist/pipeline/deep-agent.js.map +1 -1
- package/dist/pipeline/simple.d.ts +29 -3
- package/dist/pipeline/simple.d.ts.map +1 -1
- package/dist/pipeline/simple.js +47 -17
- package/dist/pipeline/simple.js.map +1 -1
- package/docs/ARCHITECTURE.md +31 -26
- package/docs/MODULES.md +5 -3
- package/docs/SCALABILITY.md +5 -4
- package/package.json +1 -3
package/Readme.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="assets/banner.svg" alt="human-to-code —
|
|
2
|
+
<img src="assets/banner.svg" alt="human-to-code — write intent in plain language, compile it to real code" width="100%">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
<a href="CONTRIBUTING.md"><img alt="contributions welcome" src="https://img.shields.io/badge/contributions-welcome-brightgreen"></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
|
-
`human-to-code`
|
|
12
|
+
`human-to-code` turns natural-language change requests — whole `.human` files or inline `@human` markers — into code. It has two entry points:
|
|
13
|
+
|
|
14
|
+
- **Direct converter** (the default `npx human-to-code .`): discovers each request and turns it straight into code, with a receipt and confirmation. Fast, works with small local models, and writes to the working tree **without** a change contract or sandbox validation.
|
|
15
|
+
- **Guided pipeline** (`human-to-code guided`): the security-constrained, production-architecture path. It statically analyzes the host project, binds the request to a versioned JSON change contract, limits what an LLM can see and request, validates the candidate in an isolated snapshot, and keeps application as a separate explicit action.
|
|
16
|
+
|
|
17
|
+
The guided pipeline is shown below:
|
|
13
18
|
|
|
14
19
|
```text
|
|
15
20
|
static project analysis
|
|
@@ -25,12 +30,12 @@ unchanged baseline vs candidate in a strong sandbox
|
|
|
25
30
|
reviewed diff ── explicit apply only after VERIFIED ── exact rollback artifact
|
|
26
31
|
```
|
|
27
32
|
|
|
28
|
-
This is not a universal deterministic source-to-source compiler. The LLM writes framework-specific patch operations, but it does not choose its own scope, validation commands, credentials, documentation sources, or acceptance criteria.
|
|
33
|
+
This is not a universal deterministic source-to-source compiler. The LLM writes framework-specific patch operations, but it does not choose its own scope, validation commands, credentials, documentation sources, or acceptance criteria. The `.strict.human.json` change contract, `VERIFIED` status, and `apply`/`rollback` machinery all belong to the guided pipeline — the default direct converter does not use them.
|
|
29
34
|
|
|
30
35
|
## Release status
|
|
31
36
|
|
|
32
37
|
> [!WARNING]
|
|
33
|
-
>
|
|
38
|
+
> This `0.1.x` release is a production-architecture **preview**, not a certified production release.
|
|
34
39
|
|
|
35
40
|
The shipped React, NestJS, FastAPI, and Rust profiles are currently `preview` (CRA is `legacy`), and no provider/model certification benchmark entry is enabled by the CLI. Therefore generated runs in this release cannot honestly become `VERIFIED`; even a clean strong-sandbox validation remains `INCONCLUSIVE`, and `apply` (therefore `rollback`) stays unreachable through a normal preview CLI run. This fail-closed behavior is intentional.
|
|
36
41
|
|
|
@@ -46,26 +51,32 @@ The published-package entry point is:
|
|
|
46
51
|
npx human-to-code .
|
|
47
52
|
```
|
|
48
53
|
|
|
49
|
-
|
|
54
|
+
By default this runs the **direct converter**, not an instruction to rewrite the whole directory. It:
|
|
55
|
+
|
|
56
|
+
1. Loads config and scans the project for `.human` files and inline `@human` markers, without importing application modules or executing project configuration.
|
|
57
|
+
2. Prints a receipt (language, provider, model, and the exact worklist) and asks for confirmation. Nothing is written until you confirm — or pass `-y`/`--yes`. If no request is found it returns `NEEDS_INPUT`.
|
|
58
|
+
3. On confirmation, converts each item with one model completion per unit and writes the result: a whole `.human` file becomes a sibling source file; an inline `@human` marker is replaced in place. Each item is independent — a failing one is skipped with a reason rather than aborting the rest.
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
2. It finds one `.human` change request. If none or several are found, it returns `NEEDS_INPUT`.
|
|
53
|
-
3. On the first run, it writes a review draft named `foo.strict.human.json` next to `foo.human` and exits with code `3`.
|
|
54
|
-
4. You review the target workspaces and symbols, allowed paths and operations, acceptance criteria, risks, and unresolved questions. A material unresolved question blocks generation.
|
|
55
|
-
5. Before any provider request, it scans first-party repository files—including ignored/untracked fixtures and logs—for credential-like content. A finding returns `SECURITY_BLOCKED`; an incomplete scan fails closed.
|
|
56
|
-
6. On the next run, it builds an outbound context manifest, requests a structured patch from the selected provider, and validates an isolated baseline and candidate.
|
|
57
|
-
7. It prints a diff and report. It never applies the patch as part of the guided command.
|
|
60
|
+
The direct converter writes code straight to the working tree. It does **not** create a `.strict.human.json` change contract, run sandbox validation, produce a `VERIFIED` run, or perform the guided pipeline's repository-wide secret scan — only the indexed declarations it attaches to an inline prompt as context are secret-scanned (a finding is `SECURITY_BLOCKED`). For the reviewed contract → grounding → sandbox-validation lifecycle — where the `.strict.human.json` contract and the `VERIFIED`/`apply`/`rollback` machinery live — use `human-to-code guided` (see [The reviewed change contract](#the-reviewed-change-contract) and the [CLI](#cli) table).
|
|
58
61
|
|
|
59
|
-
No configured provider is needed
|
|
62
|
+
No configured provider is needed to scan and preview. A default run selects loopback-local Ollama with `qwen2.5-coder:7b`, so a fresh `npx human-to-code .` never transmits code remotely by default. The model must already be installed; the tool never pulls it implicitly. To use another local model, OpenAI, or Ollama Cloud, create and edit a config (the command never overwrites one):
|
|
60
63
|
|
|
61
64
|
```bash
|
|
62
65
|
npx human-to-code --init .
|
|
63
66
|
# Edit human-to-code.config.json: select OpenAI, local Ollama, or Ollama Cloud.
|
|
64
67
|
```
|
|
65
68
|
|
|
66
|
-
|
|
69
|
+
Remote OpenAI/Ollama Cloud generation needs a config because `privacy.remoteProviderConsent` defaults to `false` and has no consent-enabling CLI flag.
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
Direct-converter example (inline marker converted in place):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
printf '// @human add a function named health that returns { status: 200 }\n' > health.ts
|
|
75
|
+
npx human-to-code . --yes --model qwen2.5-coder:7b
|
|
76
|
+
# health.ts now contains the generated function in place of the marker.
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Guided-pipeline example (reviewed contract, no code written until VERIFIED):
|
|
69
80
|
|
|
70
81
|
```bash
|
|
71
82
|
cat > add-health-route.human <<'EOF'
|
|
@@ -73,9 +84,8 @@ Add a health endpoint using the existing routing, response, authentication,
|
|
|
73
84
|
logging, and test conventions. It must not expose secrets or tenant data.
|
|
74
85
|
EOF
|
|
75
86
|
|
|
76
|
-
npx human-to-code . --provider ollama --model qwen2.5-coder:14b
|
|
77
|
-
# Review add-health-route.strict.human.json, resolve REVIEW-1, then rerun
|
|
78
|
-
npx human-to-code . --file add-health-route.human --provider ollama --model qwen2.5-coder:14b
|
|
87
|
+
npx human-to-code guided . --provider ollama --model qwen2.5-coder:14b
|
|
88
|
+
# Review add-health-route.strict.human.json, resolve REVIEW-1, then rerun the same command.
|
|
79
89
|
```
|
|
80
90
|
|
|
81
91
|
From a source checkout:
|
|
@@ -91,51 +101,69 @@ Node.js 24 or newer is required.
|
|
|
91
101
|
|
|
92
102
|
## Generation engines
|
|
93
103
|
|
|
94
|
-
The
|
|
104
|
+
The direct `npx human-to-code .` flow discovers work (whole `.human` files and
|
|
95
105
|
inline `@human` markers), prints a receipt, and — after you confirm — converts
|
|
96
|
-
it
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
106
|
+
it. Two engines are available.
|
|
107
|
+
|
|
108
|
+
### Default: fast deterministic engine
|
|
109
|
+
|
|
110
|
+
The host does the orchestration; the model only writes code. For each unit the
|
|
111
|
+
host issues **one plain model completion** (no tool calls) and applies the
|
|
112
|
+
result by exact marker range. This is fast and works with small models — a 1.5B
|
|
113
|
+
coder model converts a four-marker file in ~4 seconds. Because it needs no
|
|
114
|
+
tool-calling, models that can only do plain text generation work fine.
|
|
115
|
+
|
|
116
|
+
- **Per-marker isolation** — each `@human` marker is generated and applied
|
|
117
|
+
independently. If one marker's output is bad (e.g. a small model redeclares an
|
|
118
|
+
existing symbol), that marker is retried, then skipped with a printed reason;
|
|
119
|
+
the other markers still convert. One failure never aborts the run.
|
|
120
|
+
- **FileMemory** — declarations already in the file are shown to the model as
|
|
121
|
+
read-only context (with few-shot examples) so it reuses rather than
|
|
122
|
+
re-declares them.
|
|
123
|
+
- **Clear, stable output** — a single truncated status line per marker
|
|
124
|
+
(`✓ app.ts (inline @human, line 12)` / `⊘ skipped … : <reason>`), plus an
|
|
125
|
+
in-place elapsed spinner while a completion is in flight.
|
|
126
|
+
|
|
127
|
+
This engine cannot produce `VERIFIED` runs; for the reviewed, sandbox-validated
|
|
128
|
+
pipeline use `human-to-code guided`.
|
|
129
|
+
|
|
130
|
+
### `--agent`: LangGraph deep agent
|
|
131
|
+
|
|
132
|
+
`--agent` runs a LangChain/LangGraph agent with an explicit coding-tool surface:
|
|
133
|
+
`Task`, `Bash`, `Glob`, `Grep`,
|
|
134
|
+
`LS`, `ExitPlanMode`, `Read`, `Edit`, `MultiEdit`, `Write`, `NotebookEdit`,
|
|
135
|
+
`WebFetch`, `TodoWrite`, `WebSearch`, `BashOutput`, and `KillBash`. File tools
|
|
136
|
+
are project-confined, reads are required before edits, protected paths are
|
|
137
|
+
denied, multi-edits are atomic, shell output is bounded, and secret-bearing
|
|
138
|
+
environment variables are removed from commands.
|
|
139
|
+
|
|
140
|
+
In an interactive terminal, agent mode uses a two-column dashboard: the current
|
|
141
|
+
todo list stays on the left while tool calls, command output, and git-style
|
|
142
|
+
green `+` / red `-` file changes stream on the right. Non-TTY and `--json`
|
|
143
|
+
usage remains line-oriented and machine-readable.
|
|
144
|
+
|
|
145
|
+
The agent prompt includes concrete JSON tool-call examples intended to help
|
|
146
|
+
very small models, including 0.8B-class models. Model size is not treated as a
|
|
147
|
+
capability guarantee: the model must reliably support structured tool calling.
|
|
148
|
+
If a model repeats the same call four times, claims completion without changing
|
|
149
|
+
every worklist item, or reaches the bounded graph step limit, the run stops with
|
|
150
|
+
a concise incomplete result instead of continuing to the framework recursion
|
|
151
|
+
error. When the host observes that every discovered item is implemented, it
|
|
152
|
+
stops the graph even if the model forgets to provide a final response.
|
|
153
|
+
|
|
154
|
+
The engine is reached through the OpenAI-compatible chat client (Ollama via its
|
|
155
|
+
`/v1` endpoint). Its `Bash` tool executes development commands on the host from
|
|
156
|
+
the project root; it is bounded and guarded but is not a strong sandbox. Use
|
|
157
|
+
the guided pipeline when hash-bound patches and container validation are
|
|
158
|
+
required. Agent mode pulls the runtime dependencies `langchain`,
|
|
159
|
+
`@langchain/core`, and `@langchain/openai`.
|
|
132
160
|
|
|
133
161
|
```bash
|
|
134
|
-
#
|
|
135
|
-
npx human-to-code . --yes --model qwen2.5-coder:
|
|
162
|
+
# Fast deterministic engine (default) — works with small models:
|
|
163
|
+
npx human-to-code . --yes --model qwen2.5-coder:1.5b
|
|
136
164
|
|
|
137
|
-
#
|
|
138
|
-
npx human-to-code . --yes --
|
|
165
|
+
# Agent dashboard — needs reliable JSON tool calling:
|
|
166
|
+
npx human-to-code . --yes --agent --model qwen2.5-coder:7b
|
|
139
167
|
```
|
|
140
168
|
|
|
141
169
|
## The reviewed change contract
|
|
@@ -156,8 +184,8 @@ The draft is deliberately conservative. Do not remove its review question until
|
|
|
156
184
|
|
|
157
185
|
| Command | Behavior |
|
|
158
186
|
| --- | --- |
|
|
159
|
-
| `human-to-code [root]` | Default direct flow: discover `.human` files and `@human` markers, show a receipt, and on confirmation convert them with the **
|
|
160
|
-
| `human-to-code [root] --
|
|
187
|
+
| `human-to-code [root]` | Default direct flow: discover `.human` files and `@human` markers, show a receipt, and on confirmation convert them with the **fast deterministic engine** (one plain model completion per marker; see below). `npx human-to-code .` is the normal entry point. |
|
|
188
|
+
| `human-to-code [root] --agent` | Same direct flow using the tool-rich **LangGraph agent dashboard** (todos, filesystem, shell, diffs, and subagents). Needs reliable structured tool calling; model size alone is not a capability guarantee. |
|
|
161
189
|
| `human-to-code guided [root]` | Reviewed contract → grounding → sandbox-validation lifecycle. The only path that can reach `VERIFIED`. |
|
|
162
190
|
| `human-to-code analyze [root] [--json]` | Produce a deterministic multi-workspace project profile and diagnostics. `SUPPORTED` means statically recognized, not certified. |
|
|
163
191
|
| `human-to-code plan <file.human> [--root <root>]` | Write a review-blocked `ChangeContractV1` draft. |
|
|
@@ -415,7 +443,7 @@ Captured stdout and stderr are scanned before report persistence. If either cont
|
|
|
415
443
|
|
|
416
444
|
No strong sandbox means no project command runs and the result is `INCONCLUSIVE`. Baseline failures, fail-then-pass flakiness, pending manual checks, unavailable toolchains/services, or mixed ecosystems without an explicit multi-toolchain image also prevent verification.
|
|
417
445
|
|
|
418
|
-
The
|
|
446
|
+
The `human-to-code guided` flow may ask the same provider/model for at most two diagnostic repairs, further limited by `budgets.maxRepairs`. Repairs run only for deterministic candidate regressions after a healthy baseline in the strong sandbox. They cannot run for security findings, infrastructure/toolchain failures, timeouts, flaky/truncated output, or unavailable services, and they cannot change paths/operation kinds, provenance, requirement coverage, dependencies, lockfiles, test operations, validation configuration, or remove existing proposed test obligations. Every accepted repair is safety-checked and validated from fresh baseline/candidate copies.
|
|
419
447
|
|
|
420
448
|
Repair checkpoints preserve cumulative request/token/cost/repair usage, provider identity, request IDs, immutable input hashes, attempt patches/diffs, and reports. A crash cannot reset the allowance. The standalone `validate <run-id>` command deliberately has no provider credentials and therefore only revalidates the stored patch; an embedding caller can continue any remaining repair allowance only by supplying the exact original provider and persisted configuration.
|
|
421
449
|
|
package/assets/banner.svg
CHANGED
|
@@ -16,23 +16,18 @@
|
|
|
16
16
|
<text x="640" y="118" text-anchor="middle" class="mono" font-size="62" font-weight="700" fill="#f8fafc">human-to-code</text>
|
|
17
17
|
<text x="640" y="158" text-anchor="middle" class="sans" font-size="21" fill="#94a3b8">Write intent in plain language. Compile it to real code.</text>
|
|
18
18
|
|
|
19
|
-
<!-- pipeline: human ->
|
|
19
|
+
<!-- pipeline: human -> code (the guided reviewed/validated path is optional) -->
|
|
20
20
|
<g>
|
|
21
|
-
<rect x="
|
|
22
|
-
<text x="
|
|
21
|
+
<rect x="445" y="212" width="170" height="62" rx="14" fill="#f59e0b"/>
|
|
22
|
+
<text x="530" y="251" text-anchor="middle" class="mono" font-size="24" font-weight="700" fill="#1c1206">human</text>
|
|
23
23
|
|
|
24
|
-
<text x="
|
|
24
|
+
<text x="640" y="252" text-anchor="middle" class="sans" font-size="30" fill="#64748b">→</text>
|
|
25
25
|
|
|
26
|
-
<rect x="
|
|
27
|
-
<text x="
|
|
28
|
-
|
|
29
|
-
<text x="785" y="252" text-anchor="middle" class="sans" font-size="30" fill="#64748b">→</text>
|
|
30
|
-
|
|
31
|
-
<rect x="810" y="212" width="170" height="62" rx="14" fill="#34d399"/>
|
|
32
|
-
<text x="895" y="251" text-anchor="middle" class="mono" font-size="24" font-weight="700" fill="#062018">code</text>
|
|
26
|
+
<rect x="665" y="212" width="170" height="62" rx="14" fill="#34d399"/>
|
|
27
|
+
<text x="750" y="251" text-anchor="middle" class="mono" font-size="24" font-weight="700" fill="#062018">code</text>
|
|
33
28
|
</g>
|
|
34
29
|
|
|
35
|
-
<text x="
|
|
36
|
-
<text x="
|
|
37
|
-
<text x="
|
|
30
|
+
<text x="530" y="304" text-anchor="middle" class="sans" font-size="15" fill="#64748b">natural language</text>
|
|
31
|
+
<text x="750" y="304" text-anchor="middle" class="sans" font-size="15" fill="#64748b">TS · JS · Python</text>
|
|
32
|
+
<text x="640" y="336" text-anchor="middle" class="sans" font-size="14" fill="#475569">optional guided pipeline → reviewed contract → sandbox-validated patch</text>
|
|
38
33
|
</svg>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 640" width="1280" height="640" role="img" aria-labelledby="title desc">
|
|
2
|
-
<title id="title">human-to-code —
|
|
2
|
+
<title id="title">human-to-code — plain language to real code</title>
|
|
3
3
|
<desc id="desc">Social sharing card for human-to-code.</desc>
|
|
4
4
|
<defs>
|
|
5
5
|
<linearGradient id="bg" x1="80" y1="40" x2="1200" y2="620" gradientUnits="userSpaceOnUse">
|
|
@@ -24,19 +24,19 @@
|
|
|
24
24
|
<rect x="58" y="45" width="12" height="38" rx="6" fill="#ff5f3c"/>
|
|
25
25
|
</g>
|
|
26
26
|
<text x="258" y="177" fill="#f7f4ef" font-family="Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif" font-size="64" font-weight="850" letter-spacing="-3">human-to-code</text>
|
|
27
|
-
<text x="91" y="315" fill="#f7f4ef" font-family="Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif" font-size="50" font-weight="760" letter-spacing="-1.6">
|
|
28
|
-
<text x="91" y="376" fill="#ff8a5c" font-family="Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif" font-size="50" font-weight="760" letter-spacing="-1.6">
|
|
29
|
-
<text x="91" y="432" fill="#aaa6b0" font-family="Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif" font-size="23">
|
|
27
|
+
<text x="91" y="315" fill="#f7f4ef" font-family="Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif" font-size="50" font-weight="760" letter-spacing="-1.6">Plain language.</text>
|
|
28
|
+
<text x="91" y="376" fill="#ff8a5c" font-family="Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif" font-size="50" font-weight="760" letter-spacing="-1.6">Real code.</text>
|
|
29
|
+
<text x="91" y="432" fill="#aaa6b0" font-family="Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif" font-size="23">Turns .human files and @human markers straight into real code.</text>
|
|
30
30
|
<g transform="translate(91 500)" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="18" font-weight="700">
|
|
31
31
|
<rect width="142" height="48" rx="24" fill="#f7f4ef" fill-opacity=".08" stroke="#fff" stroke-opacity=".12"/>
|
|
32
32
|
<text x="71" y="31" text-anchor="middle" fill="#f7f4ef">.human</text>
|
|
33
33
|
<path d="M155 24h42" stroke="#ff5f3c" stroke-width="3" stroke-linecap="round"/>
|
|
34
34
|
<path d="m190 17 8 7-8 7" fill="none" stroke="#ff5f3c" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
35
35
|
<rect x="211" width="168" height="48" rx="24" fill="#f7f4ef" fill-opacity=".08" stroke="#fff" stroke-opacity=".12"/>
|
|
36
|
-
<text x="295" y="31" text-anchor="middle" fill="#f7f4ef">
|
|
36
|
+
<text x="295" y="31" text-anchor="middle" fill="#f7f4ef">generate</text>
|
|
37
37
|
<path d="M392 24h42" stroke="#ff5f3c" stroke-width="3" stroke-linecap="round"/>
|
|
38
38
|
<path d="m427 17 8 7-8 7" fill="none" stroke="#ff5f3c" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
39
39
|
<rect x="448" width="182" height="48" rx="24" fill="#ff5f3c"/>
|
|
40
|
-
<text x="539" y="31" text-anchor="middle" fill="#13090b">
|
|
40
|
+
<text x="539" y="31" text-anchor="middle" fill="#13090b">code</text>
|
|
41
41
|
</g>
|
|
42
42
|
</svg>
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,sGAAsG;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,sGAAsG;AA0wBtG,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiFzD"}
|
package/dist/cli.js
CHANGED
|
@@ -15,12 +15,13 @@ import { ProviderError } from "./providers/provider.js";
|
|
|
15
15
|
import { createOllamaProvider, createOpenAIProvider } from "./providers/providers.js";
|
|
16
16
|
import { RunStore } from "./pipeline/run-store.js";
|
|
17
17
|
import { applyUnit, discoverUnits, generateConversionUnits, generateCode, renderReceipt, } from "./pipeline/simple.js";
|
|
18
|
+
import { createAgentTerminal } from "./pipeline/agent-tui.js";
|
|
18
19
|
import { applyVerifiedRun, buildContextPreview, generateRun, resolveWorkspaceConfig, rollbackAppliedRun, validateStoredRun, } from "./pipeline/workflow.js";
|
|
19
20
|
const HELP = `human-to-code — reviewed, grounded, isolated human-to-code compiler agent
|
|
20
21
|
|
|
21
22
|
Usage:
|
|
22
|
-
human-to-code [root] [-y] Convert .human files and @human markers to code (default;
|
|
23
|
-
human-to-code [root] [-y] --
|
|
23
|
+
human-to-code [root] [-y] Convert .human files and @human markers to code (default; fast deterministic engine)
|
|
24
|
+
human-to-code [root] [-y] --agent Use the agent dashboard (todos/tools/commands/diffs; needs reliable JSON tool calling)
|
|
24
25
|
human-to-code build [root] [-y] Alias of the default convert flow
|
|
25
26
|
human-to-code guided [root] Reviewed/grounded/validated compiler pipeline
|
|
26
27
|
human-to-code analyze [root] [--json]
|
|
@@ -51,7 +52,8 @@ Other options:
|
|
|
51
52
|
--explain Show outbound context provenance and exact selected content
|
|
52
53
|
--json Machine-readable output
|
|
53
54
|
-y, --yes Skip the confirmation prompt and write files
|
|
54
|
-
--
|
|
55
|
+
--agent Use the interactive agent dashboard instead of the deterministic engine
|
|
56
|
+
--simple Deprecated no-op; the deterministic engine is now the default
|
|
55
57
|
--dry-run Analyze and preview only; perform no generation
|
|
56
58
|
--manual-passed Explicitly attest all reviewed manual acceptance checks
|
|
57
59
|
--sandbox-image <image> Trusted image reference that must already exist locally
|
|
@@ -83,6 +85,7 @@ function parse(argv) {
|
|
|
83
85
|
"trust-custom-endpoint": { type: "boolean", default: false },
|
|
84
86
|
yes: { type: "boolean", short: "y", default: false },
|
|
85
87
|
simple: { type: "boolean", default: false },
|
|
88
|
+
agent: { type: "boolean", default: false },
|
|
86
89
|
init: { type: "boolean", default: false },
|
|
87
90
|
help: { type: "boolean", short: "h", default: false },
|
|
88
91
|
root: { type: "string" },
|
|
@@ -108,6 +111,7 @@ function parse(argv) {
|
|
|
108
111
|
trustCustomEndpoint: values["trust-custom-endpoint"] === true,
|
|
109
112
|
yes: values.yes === true,
|
|
110
113
|
simple: values.simple === true,
|
|
114
|
+
agent: values.agent === true,
|
|
111
115
|
init: values.init === true,
|
|
112
116
|
help: values.help === true,
|
|
113
117
|
...(typeof values.root === "string" ? { root: values.root } : {}),
|
|
@@ -432,11 +436,19 @@ function createSpinner(active) {
|
|
|
432
436
|
const started = Date.now();
|
|
433
437
|
let index = 0;
|
|
434
438
|
let text = "working";
|
|
439
|
+
const columns = () => (typeof process.stderr.columns === "number" && process.stderr.columns > 12 ? process.stderr.columns : 80);
|
|
435
440
|
const clear = () => { process.stderr.write("\r[K"); };
|
|
436
441
|
const tick = () => {
|
|
437
442
|
index = (index + 1) % frames.length;
|
|
438
443
|
const seconds = Math.round((Date.now() - started) / 1000);
|
|
439
|
-
|
|
444
|
+
let line = `${frames[index] ?? ""} ${text} · ${seconds}s`;
|
|
445
|
+
// Truncate to one terminal row: a wider line wraps, and the carriage-return
|
|
446
|
+
// clear then cannot erase the wrapped remainder — that is what made the
|
|
447
|
+
// spinner spew a new line per frame.
|
|
448
|
+
const max = columns() - 1;
|
|
449
|
+
if (line.length > max)
|
|
450
|
+
line = `${line.slice(0, max - 1)}…`;
|
|
451
|
+
process.stderr.write(`\r\x1b[K${line}`);
|
|
440
452
|
};
|
|
441
453
|
const timer = setInterval(tick, 120);
|
|
442
454
|
if (typeof timer.unref === "function")
|
|
@@ -447,18 +459,11 @@ function createSpinner(active) {
|
|
|
447
459
|
stop: () => { clearInterval(timer); clear(); },
|
|
448
460
|
};
|
|
449
461
|
}
|
|
450
|
-
function todoIcon(status) {
|
|
451
|
-
if (status === "completed")
|
|
452
|
-
return "[✓]";
|
|
453
|
-
if (status === "in_progress")
|
|
454
|
-
return "[~]";
|
|
455
|
-
return "[ ]";
|
|
456
|
-
}
|
|
457
462
|
/** Rewrite an incapable-model tool-call failure into an actionable hint. */
|
|
458
463
|
function deepAgentErrorHint(message) {
|
|
459
464
|
const looksLikeToolFormatFailure = /xml syntax error|<\/?(?:function|parameter|tool_call)>|failed to parse|does not support tools|no tool/iu.test(message);
|
|
460
465
|
return looksLikeToolFormatFailure
|
|
461
|
-
? " — the model
|
|
466
|
+
? " — the model could not emit valid JSON tool calls. Try the deterministic default mode or a stronger tool-calling model."
|
|
462
467
|
: "";
|
|
463
468
|
}
|
|
464
469
|
/**
|
|
@@ -489,7 +494,7 @@ async function buildCommand(cli, rootInput) {
|
|
|
489
494
|
}
|
|
490
495
|
}
|
|
491
496
|
else {
|
|
492
|
-
output(renderReceipt(units, providerName, model, language, cli.
|
|
497
|
+
output(renderReceipt(units, providerName, model, language, cli.agent ? "agent" : "simple"), false);
|
|
493
498
|
}
|
|
494
499
|
if (units.length === 0)
|
|
495
500
|
return 3;
|
|
@@ -507,53 +512,20 @@ async function buildCommand(cli, rootInput) {
|
|
|
507
512
|
const apiKey = providerName === "openai"
|
|
508
513
|
? process.env[effective.provider.apiKeyEnv ?? "OPENAI_API_KEY"]
|
|
509
514
|
: undefined;
|
|
510
|
-
const
|
|
515
|
+
const localProvider = providerName === "ollama"
|
|
511
516
|
&& (baseUrl === undefined || isLoopbackProviderHost(new URL(baseUrl).hostname));
|
|
512
|
-
if (
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
// prompts). `--simple` selects the deterministic, dependency-free generator.
|
|
519
|
-
if (!cli.simple) {
|
|
517
|
+
if (!localProvider && !effective.privacy.remoteProviderConsent) {
|
|
518
|
+
throw new ContextSecurityError("INVALID_CANDIDATE", "Direct conversion would send change instructions and possibly source context to a remote provider. Review the provider and set privacy.remoteProviderConsent to true first.");
|
|
519
|
+
}
|
|
520
|
+
// `--agent` opts into the tool-rich LangGraph agent dashboard. The default
|
|
521
|
+
// remains the deterministic per-marker generator below.
|
|
522
|
+
if (cli.agent) {
|
|
520
523
|
const { runDeepAgentConversion } = await import("./pipeline/deep-agent.js");
|
|
521
|
-
const interactive = !cli.json;
|
|
522
|
-
const spinner = createSpinner(interactive);
|
|
523
524
|
const started = Date.now();
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
? (event) => {
|
|
528
|
-
if (event.kind === "plan") {
|
|
529
|
-
if (!planPrinted) {
|
|
530
|
-
spinner.note("\nPlan:");
|
|
531
|
-
for (const todo of event.todos) {
|
|
532
|
-
spinner.note(` ${todoIcon(todo.status)} ${todo.content}`);
|
|
533
|
-
todoStatus.set(todo.content, todo.status);
|
|
534
|
-
}
|
|
535
|
-
planPrinted = true;
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
for (const todo of event.todos) {
|
|
539
|
-
if (todoStatus.get(todo.content) !== todo.status) {
|
|
540
|
-
if (todo.status === "completed")
|
|
541
|
-
spinner.note(` ✓ ${todo.content}`);
|
|
542
|
-
else if (todo.status === "in_progress")
|
|
543
|
-
spinner.label(todo.content);
|
|
544
|
-
todoStatus.set(todo.content, todo.status);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
else if (event.kind === "tool") {
|
|
549
|
-
const detail = event.detail ? ` ${event.detail}` : "";
|
|
550
|
-
spinner.note(` → ${event.name}${detail}`);
|
|
551
|
-
spinner.label(`${event.name}${detail}`);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
525
|
+
const terminal = cli.json ? undefined : createAgentTerminal(`human-to-code agent • ${model} • ${basename(root)}`);
|
|
526
|
+
const onProgress = terminal
|
|
527
|
+
? (event) => terminal.progress(event)
|
|
554
528
|
: undefined;
|
|
555
|
-
if (interactive)
|
|
556
|
-
output("\nStarting deep agent… (planning, filesystem, subagents)", false);
|
|
557
529
|
try {
|
|
558
530
|
const outcome = await runDeepAgentConversion({
|
|
559
531
|
root,
|
|
@@ -565,30 +537,50 @@ async function buildCommand(cli, rootInput) {
|
|
|
565
537
|
...(apiKey ? { apiKey } : {}),
|
|
566
538
|
...(onProgress ? { onProgress } : {}),
|
|
567
539
|
});
|
|
568
|
-
|
|
540
|
+
terminal?.stop();
|
|
569
541
|
const seconds = Math.round((Date.now() - started) / 1000);
|
|
570
542
|
if (cli.json) {
|
|
571
|
-
output({ status: "DONE", engine: "deep-agent", todos: outcome.todos, messages: outcome.messageCount, summary: outcome.summary }, true);
|
|
543
|
+
output({ status: "DONE", engine: "deep-agent", stopReason: outcome.stopReason, todos: outcome.todos, messages: outcome.messageCount, tools: outcome.toolCount, summary: outcome.summary }, true);
|
|
572
544
|
}
|
|
573
545
|
else {
|
|
574
|
-
output(
|
|
546
|
+
output(`Agent finished in ${seconds}s • ${outcome.toolCount} tools • ${outcome.stopReason.replaceAll("_", " ")}.${outcome.summary ? `\n${outcome.summary}` : ""}`, false);
|
|
575
547
|
}
|
|
576
548
|
return 0;
|
|
577
549
|
}
|
|
578
550
|
catch (error) {
|
|
579
|
-
|
|
551
|
+
terminal?.stop();
|
|
580
552
|
const message = error instanceof Error ? error.message : String(error);
|
|
581
553
|
output(cli.json
|
|
582
554
|
? { status: "FAILED", engine: "deep-agent", error: message }
|
|
583
|
-
:
|
|
555
|
+
: `Agent stopped: ${message}${deepAgentErrorHint(message)}`, cli.json);
|
|
584
556
|
return 5;
|
|
585
557
|
}
|
|
586
558
|
}
|
|
559
|
+
// Default engine: deterministic per-marker generation. Each marker is one
|
|
560
|
+
// plain model completion (no tool calls), applied by exact range. One marker
|
|
561
|
+
// failing never aborts the others.
|
|
562
|
+
const describeUnit = (unit) => unit.kind === "file"
|
|
563
|
+
? `${unit.sourcePath} → ${unit.outputPath}`
|
|
564
|
+
: `${unit.sourcePath} (inline @human, line ${unit.line ?? "?"})`;
|
|
565
|
+
const interactive = !cli.json;
|
|
566
|
+
const spinner = createSpinner(interactive);
|
|
567
|
+
const started = Date.now();
|
|
568
|
+
const onProgress = interactive
|
|
569
|
+
? (event) => {
|
|
570
|
+
if (event.kind === "start") {
|
|
571
|
+
const retry = event.attempt > 1 ? ` (retry ${event.attempt - 1})` : "";
|
|
572
|
+
spinner.label(`generating ${describeUnit(event.unit)}${retry}`);
|
|
573
|
+
}
|
|
574
|
+
else if (event.kind === "skip") {
|
|
575
|
+
spinner.note(` ⊘ skipped ${describeUnit(event.unit)}: ${event.reason}`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
: undefined;
|
|
579
|
+
if (interactive)
|
|
580
|
+
output(`\nConverting ${units.length} item(s) with ${model}…`, false);
|
|
587
581
|
let generated;
|
|
588
|
-
let generatingSource;
|
|
589
582
|
try {
|
|
590
583
|
generated = await generateConversionUnits(units, (unit, context) => {
|
|
591
|
-
generatingSource = unit.sourcePath;
|
|
592
584
|
if (context.fileMemory
|
|
593
585
|
&& context.fileMemory.length > effective.privacy.maxContextTokens * 4) {
|
|
594
586
|
throw new ContextSecurityError("BUDGET_EXCEEDED", `FileMemory for ${unit.sourcePath} exceeds the configured context budget.`, unit.sourcePath);
|
|
@@ -601,18 +593,19 @@ async function buildCommand(cli, rootInput) {
|
|
|
601
593
|
...(apiKey ? { apiKey } : {}),
|
|
602
594
|
...context,
|
|
603
595
|
});
|
|
604
|
-
});
|
|
596
|
+
}, { retries: 1, ...(onProgress ? { onProgress } : {}) });
|
|
605
597
|
}
|
|
606
598
|
catch (error) {
|
|
599
|
+
spinner.stop();
|
|
607
600
|
if (error instanceof ContextSecurityError)
|
|
608
601
|
throw error;
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
: `\nProvider error${generatingSource ? ` for ${generatingSource}` : ""}: ${error instanceof Error ? error.message : String(error)}`, cli.json);
|
|
602
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
603
|
+
output(cli.json ? { status: "FAILED", error: message } : `\nError: ${message}`, cli.json);
|
|
612
604
|
return 5;
|
|
613
605
|
}
|
|
614
|
-
|
|
615
|
-
// bottom-to-top so replacing later
|
|
606
|
+
spinner.stop();
|
|
607
|
+
// Apply bottom-to-top so replacing a later marker cannot invalidate an
|
|
608
|
+
// earlier marker's range.
|
|
616
609
|
const ordered = [...generated].sort((left, right) => {
|
|
617
610
|
if (left.unit.kind !== right.unit.kind)
|
|
618
611
|
return left.unit.kind === "file" ? -1 : 1;
|
|
@@ -622,19 +615,31 @@ async function buildCommand(cli, rootInput) {
|
|
|
622
615
|
return (right.unit.range?.start ?? 0) - (left.unit.range?.start ?? 0);
|
|
623
616
|
});
|
|
624
617
|
const written = [];
|
|
625
|
-
|
|
618
|
+
const skipped = [];
|
|
619
|
+
for (const { unit, code, error } of ordered) {
|
|
620
|
+
if (error !== undefined) {
|
|
621
|
+
skipped.push({ source: unit.sourcePath, reason: error });
|
|
622
|
+
continue; // already reported live via onProgress
|
|
623
|
+
}
|
|
626
624
|
if (code.trim().length === 0) {
|
|
625
|
+
skipped.push({ source: unit.sourcePath, reason: "empty model output" });
|
|
627
626
|
if (!cli.json)
|
|
628
|
-
output(`
|
|
627
|
+
output(` ⊘ skipped ${describeUnit(unit)}: empty model output`, false);
|
|
629
628
|
continue;
|
|
630
629
|
}
|
|
631
630
|
const path = await applyUnit(root, unit, code);
|
|
632
631
|
written.push(path);
|
|
633
632
|
if (!cli.json)
|
|
634
|
-
output(` ✓
|
|
633
|
+
output(` ✓ ${describeUnit(unit)}`, false);
|
|
635
634
|
}
|
|
636
|
-
|
|
637
|
-
|
|
635
|
+
const seconds = Math.round((Date.now() - started) / 1000);
|
|
636
|
+
if (cli.json) {
|
|
637
|
+
output({ status: written.length === 0 && skipped.length > 0 ? "FAILED" : "DONE", engine: "simple", written, skipped }, true);
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
output(`\nDone in ${seconds}s. ${written.length} written${skipped.length > 0 ? `, ${skipped.length} skipped` : ""}.`, false);
|
|
641
|
+
}
|
|
642
|
+
return written.length === 0 && skipped.length > 0 ? 5 : 0;
|
|
638
643
|
}
|
|
639
644
|
async function guided(cli, rootInput) {
|
|
640
645
|
const root = resolve(cli.root ?? rootInput ?? ".");
|