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 CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="assets/banner.svg" alt="human-to-code — reviewed intent to validated code patches" width="100%">
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` is a security-constrained compiler agent for turning a reviewed natural-language change request into a grounded, structured patch. It statically analyzes the host project, requires 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.
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
- > Version `0.1.0` is a production-architecture **preview**, not a certified production release.
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
- The command is a guided workflow, not an instruction to edit the current directory immediately:
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
- 1. It statically analyzes the repository without importing application modules or executing project configuration.
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 for the first analysis/draft pass. A no-file/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):
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
- Or pass `--provider ollama --model <installed-model>` on every guided local-Ollama run. Remote OpenAI/Ollama Cloud generation needs a config because `privacy.remoteProviderConsent` defaults to `false` and has no consent-enabling CLI flag.
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
- For example:
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 default `npx human-to-code .` flow discovers work (whole `.human` files and
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 with a **LangChain/LangGraph deep agent** (the [`deepagents`](https://docs.langchain.com/oss/javascript/deepagents/overview)
97
- harness). The agent runs with the four deep-agent pillars:
98
-
99
- - **Planning** — the built-in `write_todos` tool decomposes the run into an
100
- ordered, status-tracked plan.
101
- - **File System** a project-rooted `FilesystemBackend` gives the agent
102
- `ls`/`glob`/`grep`/`read_file`/`write_file`/`edit_file` over the working tree.
103
- Writes to VCS, dependency, config, and secret paths are denied by filesystem
104
- permissions; the agent cannot escape the project root.
105
- - **Sub Agents** — a `planner`, `implementer`, and `reviewer` subagent are
106
- reachable through the built-in `task` tool for isolated subtasks.
107
- - **Prompts** a task-specific system prompt for the main agent and one per
108
- subagent role.
109
-
110
- The model, not the host, drives scope, file edits, and delegation here. This is
111
- an autonomous engine: it edits files directly and does **not** produce
112
- `VERIFIED` runs, hash-verified patches, or run-store records. For the reviewed,
113
- sandbox-validated pipeline use `human-to-code guided`.
114
-
115
- While it runs, the agent streams live progress to the terminal: an animated
116
- elapsed-time spinner plus a line for the plan and each tool call
117
- (`→ read_file …`, `→ edit_file …`, `✓ <done step>`), so a long run is never a
118
- blank screen.
119
-
120
- The provider is bound through the OpenAI-compatible chat client. Ollama is
121
- reached at its `/v1` endpoint (the deep agent's structured tool messages are not
122
- supported by the native `/api/chat` surface), so the model you pass must be
123
- tool-calling capable. Small models that cannot emit valid tool calls fail with a
124
- tool-call/XML parse error; the CLI detects this and suggests a larger model or
125
- `--simple`. In practice a ~7B-or-larger coder model is the floor; sub-2B models
126
- do not work.
127
-
128
- Unlike the rest of the tool, this engine adds runtime dependencies
129
- (`deepagents`, `langchain`, `@langchain/core`, `@langchain/openai`,
130
- `@langchain/ollama`). Pass `--simple` to convert with the built-in-only
131
- generator instead, which keeps the run's supply-chain surface at Node itself.
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
- # Deep agent (default), with a tool-calling local model:
135
- npx human-to-code . --yes --model qwen2.5-coder:7b
162
+ # Fast deterministic engine (default) works with small models:
163
+ npx human-to-code . --yes --model qwen2.5-coder:1.5b
136
164
 
137
- # Deterministic, dependency-free generator:
138
- npx human-to-code . --yes --simple
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 **deep-agent engine** (see below). `npx human-to-code .` is the normal entry point. |
160
- | `human-to-code [root] --simple` | Same direct flow using the deterministic, dependency-free generator instead of the deep agent. |
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 guided `npx human-to-code .` 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.
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 -> strict -> code -->
19
+ <!-- pipeline: human -> code (the guided reviewed/validated path is optional) -->
20
20
  <g>
21
- <rect x="300" y="212" width="170" height="62" rx="14" fill="#f59e0b"/>
22
- <text x="385" y="251" text-anchor="middle" class="mono" font-size="24" font-weight="700" fill="#1c1206">human</text>
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="495" y="252" text-anchor="middle" class="sans" font-size="30" fill="#64748b">&#8594;</text>
24
+ <text x="640" y="252" text-anchor="middle" class="sans" font-size="30" fill="#64748b">&#8594;</text>
25
25
 
26
- <rect x="520" y="212" width="240" height="62" rx="14" fill="#38bdf8"/>
27
- <text x="640" y="251" text-anchor="middle" class="mono" font-size="24" font-weight="700" fill="#062033">strict</text>
28
-
29
- <text x="785" y="252" text-anchor="middle" class="sans" font-size="30" fill="#64748b">&#8594;</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="385" y="304" text-anchor="middle" class="sans" font-size="15" fill="#64748b">natural language</text>
36
- <text x="640" y="304" text-anchor="middle" class="sans" font-size="15" fill="#64748b">reviewable IR &#183; deterministic</text>
37
- <text x="895" y="304" text-anchor="middle" class="sans" font-size="15" fill="#64748b">TS &#183; JS &#183; Python</text>
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 &#183; JS &#183; Python</text>
32
+ <text x="640" y="336" text-anchor="middle" class="sans" font-size="14" fill="#475569">optional guided pipeline &#8594; reviewed contract &#8594; 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 — reviewed intent to validated code</title>
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">Reviewed intent.</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">Validated 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">Security-constrained compiler agent for grounded, reviewable patches.</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">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">review</text>
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">verified patch</text>
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;AA+vBtG,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiFzD"}
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; LangGraph deep agent)
23
- human-to-code [root] [-y] --simple Use the deterministic, dependency-free generator instead of the deep agent
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
- --simple Use the deterministic generator instead of the deep agent (default flow only)
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"); };
436
441
  const tick = () => {
437
442
  index = (index + 1) % frames.length;
438
443
  const seconds = Math.round((Date.now() - started) / 1000);
439
- process.stderr.write(`\r${frames[index] ?? ""} ${text} · ${seconds}s`);
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 likely cannot emit valid tool calls. Use a larger tool-calling model (for example qwen2.5-coder:7b or bigger), or run with --simple."
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.simple ? "simple" : "agent"), false);
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 localMemoryProvider = providerName === "ollama"
515
+ const localProvider = providerName === "ollama"
511
516
  && (baseUrl === undefined || isLoopbackProviderHost(new URL(baseUrl).hostname));
512
- if (units.some((unit) => unit.kind === "inline")
513
- && !localMemoryProvider
514
- && !effective.privacy.remoteProviderConsent) {
515
- throw new ContextSecurityError("INVALID_CANDIDATE", "Inline FileMemory would send statically indexed source declarations to a remote provider. Review the provider and set privacy.remoteProviderConsent to true first.");
516
- }
517
- // Default engine: a LangGraph deep agent (planning, filesystem, subagents,
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 todoStatus = new Map();
525
- let planPrinted = false;
526
- const onProgress = interactive
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
- spinner.stop();
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(`\nDeep agent finished in ${seconds}s (${outcome.messageCount} steps).${outcome.summary ? `\n${outcome.summary}` : ""}`, false);
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
- spinner.stop();
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
- : `\nDeep agent error: ${message}${deepAgentErrorHint(message)}`, cli.json);
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
- output(cli.json
610
- ? { status: "FAILED", ...(generatingSource ? { source: generatingSource } : {}), error: String(error) }
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
- // Generation is top-to-bottom so FileMemory is meaningful. Application stays
615
- // bottom-to-top so replacing later markers cannot invalidate earlier ranges.
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
- for (const { unit, code } of ordered) {
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(` ! empty model output for ${unit.sourcePath}; skipped`, false);
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(` ✓ wrote ${path}`, false);
633
+ output(` ✓ ${describeUnit(unit)}`, false);
635
634
  }
636
- output(cli.json ? { status: "DONE", written } : `\nDone. ${written.length} file(s) written.`, cli.json);
637
- return 0;
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 ?? ".");