Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable Phonton CLI release changes should be documented here.

This project follows pre-1.0 SemVer: minor versions may still include breaking changes while the public API and CLI surface settle.

## 0.14.1 - Generated Web Failure Diagnostics

### Fixed

- Problems focus now shows the changed-file excerpt that matches the verifier diagnostic path. A failure such as `[typescript syntax] src/App.tsx:1:1 invalid syntax` now jumps to the `src/App.tsx` diff instead of showing the first unrelated changed file.
- Generated web syntax diagnostics that include line/column suffixes such as `src/App.tsx:1:1` are normalized back to artifact paths before retry policy runs. This keeps generic repair contexts on the generated-web fast-fail path instead of spending another broad provider repair.
- The existing Vite/React chess rules test seed no longer imports `vitest`. The seeded test file uses self-executing assertions, so it does not require Phonton to repair `package.json` before local rules verification can pass.

## 0.14.0 - Non-Interactive Node Verification

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="assets/readme/phonton-cli-logo.png" width="112" alt="Phonton CLI logo">
</p>

<h1 align="center">Phonton CLI · v0.13.5</h1>
<h1 align="center">Phonton CLI · v0.14.1</h1>

<p align="center">
<strong>Verified code changes with repo memory.</strong><br>
Expand All @@ -12,7 +12,7 @@
<p align="center">
<a href="https://github.com/phonton-dev/phonton-cli/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/phonton-dev/phonton-cli/actions/workflows/ci.yml/badge.svg"></a>
<a href="https://github.com/phonton-dev/phonton-cli/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/phonton-dev/phonton-cli?style=flat&label=stars"></a>
<img alt="release" src="https://img.shields.io/badge/release-v0.13.5-6c63ff">
<img alt="release" src="https://img.shields.io/badge/release-v0.14.1-6c63ff">
<img alt="license" src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue">
<img alt="status" src="https://img.shields.io/badge/status-public_alpha-f97316">
</p>
Expand Down Expand Up @@ -75,6 +75,8 @@ It walks through the evidence trail a real run should expose: GoalContract, plan
- `/why-tokens` and `phonton why-tokens --by-source` explain the latest prompt manifest in plain language, including first-attempt, repair-attempt, context/artifact, system, goal, memory, attachment, repo-code, MCP/tool, retry, compaction, dedupe, and cached-token buckets.
- v0.11 context planning builds a compact repo map, selects only the highest-value code slices under a target budget, exposes omitted code tokens, and labels target-exceeded prompts honestly when one required slice must go over budget.
- v0.12 enforces lower spend before the provider call: generated app/game goals dispatch as acceptance-slice subtasks, simple/docs/test prompts use small task-class budgets, generated repairs use a sub-1k context target, semantic retrieval top-k and repo maps shrink by task class, MCP result context is capped, and provider output ceilings are lower.
- v0.14.1 fixes generated-web failure diagnostics: Problems focus now jumps to the changed file named by the verifier, `src/App.tsx:1:1` diagnostics are normalized for retry policy, and the local chess rules test seed no longer requires a Vitest import.
- v0.14.0 hardens Node verification so stock Vite/Vitest/Jest test scripts run in non-interactive CI mode instead of hanging in watch mode.
- v0.13.5 seeds the existing Vite/React chess rules/test boundary with a locally verified template before provider UI slices, including recovery from partial invalid `src/chessRules.ts` artifacts without another provider call.
- v0.13.4 detects existing Vite/React workspaces for chess benchmark prompts that say "use the existing project stack," then starts on source/test slices instead of fragile `package.json` or `index.html` scaffold edits.
- v0.13.1 hardens generated web-app token behavior: Vite/React chess prompts stay on compact acceptance slices even in partial workspaces, and first-attempt TSX/HTML syntax failures stop before automatic repair.
Expand Down Expand Up @@ -153,7 +155,7 @@ Windows PowerShell:
Direct Cargo install:

```bash
cargo install --git https://github.com/phonton-dev/phonton-cli --tag v0.13.5 phonton-cli --locked --force
cargo install --git https://github.com/phonton-dev/phonton-cli --tag v0.14.1 phonton-cli --locked --force
```

Check the install:
Expand All @@ -169,7 +171,7 @@ Phonton uses GitHub branches and releases as install channels:

| Channel | Install | Use when |
|---|---|---|
| Stable | `cargo install --git https://github.com/phonton-dev/phonton-cli --tag v0.13.5 phonton-cli --locked --force` | You want the best validated public alpha |
| Stable | `cargo install --git https://github.com/phonton-dev/phonton-cli --tag v0.14.1 phonton-cli --locked --force` | You want the best validated public alpha |
| Dev | `cargo install --git https://github.com/phonton-dev/phonton-cli --branch dev phonton-cli --locked --force` | You want next-release integration changes |
| Nightly | `cargo install --git https://github.com/phonton-dev/phonton-cli --branch nightly phonton-cli --locked --force` | You want daily snapshots and can tolerate breakage |
| Main | `cargo install --git https://github.com/phonton-dev/phonton-cli --branch main phonton-cli --locked --force` | You want the current release branch tip |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "phonton-cli",
"version": "0.14.0",
"version": "0.14.1",
"description": "Local-first agentic development terminal with context packs, source handles, and verification gates.",
"license": "MIT OR Apache-2.0",
"homepage": "https://github.com/phonton-dev/phonton-cli#readme",
Expand Down
2 changes: 1 addition & 1 deletion phonton-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "phonton-cli"
version = "0.14.0"
version = "0.14.1"
edition.workspace = true
license.workspace = true
repository.workspace = true
Expand Down
31 changes: 29 additions & 2 deletions phonton-cli/src/focus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,11 @@ pub(crate) fn problems_focus_text(goal: &GoalEntry, selected_file: usize) -> Str
out.push_str("\nRepair\n- Press r or run /retry to queue a repair with compact diagnostics.\n- Use /why-tokens to inspect retry/context token buckets.\n");

let groups = diff_hunks_by_file(goal);
if let Some((path, hunks)) = groups.get(selected_file.min(groups.len().saturating_sub(1))) {
let selected_group = problem_excerpt_index(&diagnostics, &groups, selected_file);
if let Some((path, hunks)) = groups.get(selected_group) {
out.push_str(&format!(
"\nChanged excerpt {}/{}\nfile: {}\n",
selected_file.min(groups.len().saturating_sub(1)) + 1,
selected_group + 1,
groups.len(),
path.display()
));
Expand Down Expand Up @@ -309,6 +310,32 @@ pub(crate) fn problems_focus_text(goal: &GoalEntry, selected_file: usize) -> Str
out
}

fn problem_excerpt_index(
diagnostics: &[String],
groups: &[(PathBuf, Vec<DiffHunk>)],
selected_file: usize,
) -> usize {
if groups.is_empty() {
return 0;
}
let diagnostic_text = diagnostics
.iter()
.map(|item| item.replace('\\', "/").to_ascii_lowercase())
.collect::<Vec<_>>()
.join("\n");
if let Some((idx, _)) = groups.iter().enumerate().find(|(_, (path, _))| {
diagnostic_text.contains(
&path
.to_string_lossy()
.replace('\\', "/")
.to_ascii_lowercase(),
)
}) {
return idx;
}
selected_file.min(groups.len().saturating_sub(1))
}

pub(crate) fn problem_diagnostics(goal: &GoalEntry) -> Vec<String> {
let mut items = Vec::new();
for record in &goal.flight_log {
Expand Down
66 changes: 66 additions & 0 deletions phonton-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9575,6 +9575,72 @@ fn extract_id(line: &str) -> Option<String> {
assert!(app.focus_text().contains("Kimi was used"));
}

#[test]
fn problems_focus_prioritizes_changed_excerpt_for_diagnostic_file() {
let mut app = App::default();
app.goals.push(GoalEntry::new("make chess".into()));
app.apply_event(
0,
EventRecord {
task_id: TaskId::new(),
timestamp_ms: 1,
event: OrchestratorEvent::SubtaskReviewReady {
subtask_id: SubtaskId::new(),
description: "generated chess slice".into(),
tier: ModelTier::Standard,
tokens_used: 10,
token_usage: TokenUsage::estimated(10),
cost: phonton_types::CostSummary::default(),
diff_hunks: vec![
DiffHunk {
file_path: PathBuf::from("src/chessRules.ts"),
old_start: 0,
old_count: 0,
new_start: 1,
new_count: 1,
lines: vec![DiffLine::Added("export const rules = true".into())],
},
DiffHunk {
file_path: PathBuf::from("src/App.tsx"),
old_start: 0,
old_count: 0,
new_start: 1,
new_count: 1,
lines: vec![DiffLine::Added("```tsx".into())],
},
],
verify_result: VerifyResult::Fail {
layer: VerifyLayer::Syntax,
errors: vec!["[typescript syntax] src/App.tsx:1:1 invalid syntax".into()],
attempt: 1,
},
provider: ProviderKind::OpenAiCompatible,
model_name: "fixture".into(),
},
},
);
app.apply_event(
0,
EventRecord {
task_id: TaskId::new(),
timestamp_ms: 2,
event: OrchestratorEvent::VerifyFail {
subtask_id: SubtaskId::new(),
layer: VerifyLayer::Syntax,
errors: vec!["[typescript syntax] src/App.tsx:1:1 invalid syntax".into()],
attempt: 1,
},
},
);
app.apply_state(0, failed_state("syntax verification failed"));

let text = app.focus_text();

assert!(text.contains("file: src/App.tsx"), "{text}");
assert!(!text.contains("file: src/chessRules.ts"), "{text}");
assert!(text.contains("+```tsx"), "{text}");
}

#[test]
fn problems_shortcuts_open_and_retry_failed_goal() {
let mut app = App::default();
Expand Down
68 changes: 67 additions & 1 deletion phonton-worker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,8 @@ fn should_stop_before_generated_app_syntax_repair(
if !matches!(
classify_intent(&subtask.description).task_class,
TaskClass::GeneratedAppGame
) {
) && !diagnostics_name_generated_web_artifact(&subtask.description, errors)
{
return false;
}
let diagnostics = errors.join("\n").to_ascii_lowercase();
Expand All @@ -1116,6 +1117,28 @@ fn should_stop_before_generated_app_syntax_repair(
|| diagnostics.contains("src/app")
}

fn diagnostics_name_generated_web_artifact(description: &str, errors: &[String]) -> bool {
let mut paths = artifact_paths_from_subtask(description);
for path in artifact_paths_from_errors(errors) {
if !paths.iter().any(|existing| existing == &path) {
paths.push(path);
}
}
paths.iter().any(|path| {
let normalized = path
.to_string_lossy()
.replace('\\', "/")
.to_ascii_lowercase();
normalized == "index.html"
|| normalized == "src/app.tsx"
|| normalized == "src/app.jsx"
|| normalized == "src/main.tsx"
|| normalized == "src/main.jsx"
|| normalized.ends_with(".tsx")
|| normalized.ends_with(".jsx")
Comment on lines +1137 to +1138

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep fast-fail scoped to generated-app subtasks

The new artifact detector treats any diagnostic path ending in .tsx/.jsx as a generated-web artifact, so should_stop_before_generated_app_syntax_repair now fast-fails on first-attempt syntax errors even for non-generated React edits (for example, ordinary repo tasks that fail on src/App.tsx:1:1). That skips the normal repair retry loop and can prematurely fail user tasks that previously self-recovered.

Useful? React with 👍 / 👎.

})
}

fn repair_guidance_for_errors(errors: &[String]) -> Vec<String> {
if !looks_like_stale_hunk_error(errors) {
return Vec::new();
Expand Down Expand Up @@ -1336,6 +1359,7 @@ fn artifact_paths_from_errors(errors: &[String]) -> Vec<PathBuf> {
.trim_end_matches(':')
.trim_end_matches(',')
.trim_end_matches(';');
let cleaned = strip_diagnostic_location_suffix(cleaned);
if !looks_like_relative_artifact_path(cleaned) {
continue;
}
Expand All @@ -1348,6 +1372,16 @@ fn artifact_paths_from_errors(errors: &[String]) -> Vec<PathBuf> {
paths
}

fn strip_diagnostic_location_suffix(mut value: &str) -> &str {
while let Some((head, tail)) = value.rsplit_once(':') {
if head.is_empty() || tail.is_empty() || !tail.chars().all(|ch| ch.is_ascii_digit()) {
break;
}
value = head;
}
value
}

fn looks_like_relative_artifact_path(value: &str) -> bool {
if value.is_empty() || value.contains("://") {
return false;
Expand Down Expand Up @@ -2772,6 +2806,20 @@ mod tests {
let _ = std::fs::remove_dir_all(base);
}

#[test]
fn local_chess_rules_test_seed_does_not_require_test_runner_dependency() {
let template = include_str!("templates/chessRules.test.ts");

assert!(
!template.contains("from 'vitest'") && !template.contains("from \"vitest\""),
"existing Vite seed must not require adding Vitest before package.json is repaired"
);
assert!(
template.contains("runRulesSeedTests()"),
"template should still self-execute assertions when a runner discovers the file"
);
}

#[derive(Clone)]
struct BrokenTsxProvider {
calls: Arc<Mutex<u64>>,
Expand Down Expand Up @@ -2855,6 +2903,24 @@ mod tests {
let _ = std::fs::remove_dir_all(base);
}

#[test]
fn generated_web_syntax_fast_fail_uses_artifact_diagnostics_when_description_is_generic() {
let task = subtask("Repair verifier failure from previous attempt");
let errors =
vec!["Verifier Syntax: [typescript syntax] src/App.tsx:1:1 invalid syntax".into()];

assert!(
should_stop_before_generated_app_syntax_repair(
&task,
VerifyLayer::Syntax,
&errors,
1,
false
),
"web artifact syntax diagnostics should stop before another broad provider repair"
);
}

#[test]
fn repeated_diagnostic_signature_ignores_attempt_noise() {
let first = diagnostic_signature(
Expand Down
Loading