@lamentis/naome 1.2.0 → 1.2.1
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/Cargo.lock +2 -2
- package/bin/naome-node.js +2 -1579
- package/bin/naome.js +19 -5
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/dispatcher.rs +2 -1
- package/crates/naome-cli/src/main.rs +3 -0
- package/crates/naome-cli/src/quality_commands.rs +90 -2
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/decision/checks.rs +64 -0
- package/crates/naome-core/src/decision/idle.rs +67 -0
- package/crates/naome-core/src/decision/json.rs +36 -0
- package/crates/naome-core/src/decision/states.rs +165 -0
- package/crates/naome-core/src/decision.rs +131 -353
- package/crates/naome-core/src/install_plan.rs +2 -0
- package/crates/naome-core/src/lib.rs +5 -3
- package/crates/naome-core/src/paths.rs +3 -1
- package/crates/naome-core/src/quality/adapter_support.rs +89 -0
- package/crates/naome-core/src/quality/adapters.rs +20 -67
- package/crates/naome-core/src/quality/cleanup.rs +13 -1
- package/crates/naome-core/src/quality/config.rs +8 -15
- package/crates/naome-core/src/quality/config_support.rs +24 -0
- package/crates/naome-core/src/quality/mod.rs +18 -0
- package/crates/naome-core/src/quality/scanner.rs +20 -8
- package/crates/naome-core/src/quality/structure/adapters.rs +84 -0
- package/crates/naome-core/src/quality/structure/checks/basic.rs +153 -0
- package/crates/naome-core/src/quality/structure/checks/directory.rs +144 -0
- package/crates/naome-core/src/quality/structure/checks/pairing.rs +63 -0
- package/crates/naome-core/src/quality/structure/checks.rs +124 -0
- package/crates/naome-core/src/quality/structure/classify/roles.rs +188 -0
- package/crates/naome-core/src/quality/structure/classify.rs +94 -0
- package/crates/naome-core/src/quality/structure/config.rs +89 -0
- package/crates/naome-core/src/quality/structure/defaults.rs +107 -0
- package/crates/naome-core/src/quality/structure/mod.rs +77 -0
- package/crates/naome-core/src/quality/structure/model.rs +124 -0
- package/crates/naome-core/src/quality/types.rs +3 -0
- package/crates/naome-core/src/route/builtin_checks.rs +155 -0
- package/crates/naome-core/src/route/builtin_context.rs +73 -0
- package/crates/naome-core/src/route/builtin_integrity.rs +49 -0
- package/crates/naome-core/src/route/builtin_require.rs +40 -0
- package/crates/naome-core/src/route/context.rs +180 -0
- package/crates/naome-core/src/route/execution.rs +96 -0
- package/crates/naome-core/src/route/execution_baselines.rs +146 -0
- package/crates/naome-core/src/route/execution_support.rs +57 -0
- package/crates/naome-core/src/route/execution_tasks.rs +71 -0
- package/crates/naome-core/src/route/git_ops.rs +72 -0
- package/crates/naome-core/src/route/quality_gate.rs +73 -0
- package/crates/naome-core/src/route/quality_gate_config.rs +126 -0
- package/crates/naome-core/src/route/quality_gate_snapshot.rs +69 -0
- package/crates/naome-core/src/route/worktree.rs +75 -0
- package/crates/naome-core/src/route/worktree_files.rs +32 -0
- package/crates/naome-core/src/route/worktree_plan.rs +131 -0
- package/crates/naome-core/src/route.rs +44 -1217
- package/crates/naome-core/src/verification.rs +1 -0
- package/crates/naome-core/tests/decision.rs +24 -118
- package/crates/naome-core/tests/harness_health.rs +2 -0
- package/crates/naome-core/tests/quality.rs +12 -118
- package/crates/naome-core/tests/quality_structure.rs +116 -0
- package/crates/naome-core/tests/quality_structure_adapters.rs +98 -0
- package/crates/naome-core/tests/quality_structure_policy.rs +125 -0
- package/crates/naome-core/tests/quality_structure_support/mod.rs +249 -0
- package/crates/naome-core/tests/repo_support/mod.rs +16 -0
- package/crates/naome-core/tests/repo_support/repo.rs +113 -0
- package/crates/naome-core/tests/repo_support/repo_factories.rs +99 -0
- package/crates/naome-core/tests/repo_support/repo_helpers.rs +123 -0
- package/crates/naome-core/tests/repo_support/routes.rs +81 -0
- package/crates/naome-core/tests/repo_support/verification.rs +168 -0
- package/crates/naome-core/tests/repo_support/verification_values.rs +135 -0
- package/crates/naome-core/tests/route.rs +1 -1376
- package/crates/naome-core/tests/route_baseline.rs +86 -0
- package/crates/naome-core/tests/route_completion.rs +141 -0
- package/crates/naome-core/tests/route_harness_refresh.rs +135 -0
- package/crates/naome-core/tests/route_user_diff.rs +198 -0
- package/crates/naome-core/tests/route_worktree.rs +54 -0
- package/crates/naome-core/tests/task_state.rs +60 -432
- package/crates/naome-core/tests/task_state_compact_support/repo.rs +1 -1
- package/crates/naome-core/tests/task_state_support/mod.rs +163 -0
- package/crates/naome-core/tests/task_state_support/states.rs +84 -0
- package/crates/naome-core/tests/verification.rs +4 -45
- package/crates/naome-core/tests/verification_contract.rs +22 -78
- package/crates/naome-core/tests/workflow_support/mod.rs +1 -1
- package/installer/agents.js +90 -0
- package/installer/context.js +67 -0
- package/installer/filesystem.js +166 -0
- package/installer/flows.js +84 -0
- package/installer/git-boundary.js +170 -0
- package/installer/git-hook-content.js +36 -0
- package/installer/git-hooks.js +134 -0
- package/installer/git-local.js +2 -0
- package/installer/git-shared.js +35 -0
- package/installer/harness-file-ops.js +140 -0
- package/installer/harness-files.js +56 -0
- package/installer/harness-verification.js +123 -0
- package/installer/install-plan.js +66 -0
- package/installer/main.js +25 -0
- package/installer/manifest-state.js +167 -0
- package/installer/native-build.js +24 -0
- package/installer/native-format.js +6 -0
- package/installer/native.js +162 -0
- package/installer/output.js +131 -0
- package/installer/version.js +32 -0
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +2 -1
- package/templates/naome-root/.naome/bin/check-harness-health.js +2 -2
- package/templates/naome-root/.naome/bin/check-task-state.js +2 -2
- package/templates/naome-root/.naome/bin/naome.js +25 -21
- package/templates/naome-root/.naome/manifest.json +4 -2
- package/templates/naome-root/.naome/repository-structure.json +90 -0
- package/templates/naome-root/.naome/verification.json +1 -0
- package/templates/naome-root/docs/naome/index.md +4 -3
- package/templates/naome-root/docs/naome/repository-quality.md +3 -0
- package/templates/naome-root/docs/naome/repository-structure.md +51 -0
- package/templates/naome-root/docs/naome/testing.md +2 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
use serde_json::json;
|
|
2
|
+
use std::fs;
|
|
3
|
+
|
|
4
|
+
mod repo_support;
|
|
5
|
+
|
|
6
|
+
use repo_support::{
|
|
7
|
+
assert_commit_paths, assert_isolated_worktree_ready, route_new_task, route_readme_task,
|
|
8
|
+
TestRepo,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
#[test]
|
|
12
|
+
fn dry_route_reports_auto_baseline_without_mutating() {
|
|
13
|
+
let repo = TestRepo::completed_task_with_diff("route-dry-auto");
|
|
14
|
+
let before = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
15
|
+
|
|
16
|
+
let route = route_new_task(&repo, "Start a new task for README polish.", false);
|
|
17
|
+
|
|
18
|
+
assert_eq!(
|
|
19
|
+
route.policy_action,
|
|
20
|
+
"auto_commit_completed_task_then_create_new_task"
|
|
21
|
+
);
|
|
22
|
+
assert!(!route.mutation_performed);
|
|
23
|
+
assert!(!route.can_create_task);
|
|
24
|
+
assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before);
|
|
25
|
+
assert!(!route.user_message.contains("commit_task_baseline"));
|
|
26
|
+
assert!(route.human_options.is_empty());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[test]
|
|
30
|
+
fn execute_route_auto_baselines_then_admits_next_task_and_writes_local_journal() {
|
|
31
|
+
let repo = TestRepo::completed_task_with_diff("route-execute-auto");
|
|
32
|
+
|
|
33
|
+
let route = route_new_task(&repo, "Start a new task for README polish.", true);
|
|
34
|
+
|
|
35
|
+
assert_eq!(
|
|
36
|
+
route.policy_action,
|
|
37
|
+
"auto_commit_completed_task_then_create_new_task"
|
|
38
|
+
);
|
|
39
|
+
assert!(route.mutation_performed);
|
|
40
|
+
assert!(route.can_create_task);
|
|
41
|
+
assert_eq!(route.next_decision.state, "ready_for_task");
|
|
42
|
+
assert!(route
|
|
43
|
+
.executed_actions
|
|
44
|
+
.contains(&"commit_task_baseline".to_string()));
|
|
45
|
+
assert!(repo.git_status_short().is_empty());
|
|
46
|
+
|
|
47
|
+
let journal = fs::read_to_string(repo.path().join(".naome/task-journal.jsonl")).unwrap();
|
|
48
|
+
assert!(journal.contains("\"taskId\":\"readme-task\""));
|
|
49
|
+
assert!(journal.contains("\"outcome\":\"route_auto_baseline\""));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[test]
|
|
53
|
+
fn execute_route_baselines_completed_task_and_creates_worktree_for_unrelated_user_edit() {
|
|
54
|
+
let repo = TestRepo::completed_task_with_unrelated_user_edit("route-preserve-user-edit");
|
|
55
|
+
|
|
56
|
+
let route = route_readme_task(&repo, true);
|
|
57
|
+
|
|
58
|
+
assert_eq!(
|
|
59
|
+
route.policy_action,
|
|
60
|
+
"auto_commit_completed_task_then_create_isolated_task_worktree"
|
|
61
|
+
);
|
|
62
|
+
assert!(route.mutation_performed);
|
|
63
|
+
assert!(route.user_message.contains("isolated task worktree"));
|
|
64
|
+
assert!(route.human_options.is_empty());
|
|
65
|
+
assert_ne!(route.task_root, repo.path().to_string_lossy());
|
|
66
|
+
assert_isolated_worktree_ready(&route, &repo, "M USER.md");
|
|
67
|
+
assert_commit_paths(&repo, &["README.md"], &["USER.md"]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[test]
|
|
71
|
+
fn execute_route_creates_worktree_for_dirty_repo_new_task() {
|
|
72
|
+
let repo = TestRepo::new("route-dirty-worktree");
|
|
73
|
+
repo.init_git();
|
|
74
|
+
repo.write_file("README.md", "# Baseline\n");
|
|
75
|
+
repo.write_file("USER.md", "user baseline\n");
|
|
76
|
+
repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
|
|
77
|
+
repo.git(&["add", "."]);
|
|
78
|
+
repo.git(&["commit", "-m", "baseline"]);
|
|
79
|
+
repo.write_file("USER.md", "user local edit\n");
|
|
80
|
+
|
|
81
|
+
let route = route_readme_task(&repo, true);
|
|
82
|
+
|
|
83
|
+
assert_eq!(route.policy_action, "create_isolated_task_worktree");
|
|
84
|
+
assert!(route.mutation_performed);
|
|
85
|
+
assert_isolated_worktree_ready(&route, &repo, "M USER.md");
|
|
86
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
|
|
3
|
+
use naome_core::{evaluate_route, explain_route, EvaluationOptions, RouteOptions};
|
|
4
|
+
use serde_json::json;
|
|
5
|
+
|
|
6
|
+
mod repo_support;
|
|
7
|
+
|
|
8
|
+
use repo_support::TestRepo;
|
|
9
|
+
|
|
10
|
+
#[test]
|
|
11
|
+
fn execute_route_does_not_mutate_when_prompt_blocks_commit() {
|
|
12
|
+
let repo = TestRepo::completed_task_with_diff("route-no-commit");
|
|
13
|
+
let before = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
14
|
+
|
|
15
|
+
let route = evaluate_route(
|
|
16
|
+
repo.path(),
|
|
17
|
+
"Do not commit. Start a new task after this.",
|
|
18
|
+
RouteOptions {
|
|
19
|
+
execute: true,
|
|
20
|
+
evaluation: EvaluationOptions::offline(),
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
.unwrap();
|
|
24
|
+
|
|
25
|
+
assert_eq!(route.policy_action, "block_auto_baseline_due_to_no_commit");
|
|
26
|
+
assert!(!route.mutation_performed);
|
|
27
|
+
assert!(!route.can_create_task);
|
|
28
|
+
assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before);
|
|
29
|
+
assert!(repo.git_status_short().contains("README.md"));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[test]
|
|
33
|
+
fn explicit_route_commit_baseline_leaves_unrelated_user_edit_unstaged() {
|
|
34
|
+
let repo = TestRepo::completed_task_with_diff("route-commit-task-scope");
|
|
35
|
+
repo.write_file("USER.md", "user baseline\n");
|
|
36
|
+
repo.git(&["add", "USER.md"]);
|
|
37
|
+
repo.git(&["commit", "-m", "user baseline"]);
|
|
38
|
+
repo.write_file("README.md", "# Changed\n");
|
|
39
|
+
repo.write_file("USER.md", "user local edit\n");
|
|
40
|
+
repo.git(&["add", "USER.md"]);
|
|
41
|
+
|
|
42
|
+
let route = evaluate_route(
|
|
43
|
+
repo.path(),
|
|
44
|
+
"commit_task_baseline",
|
|
45
|
+
RouteOptions {
|
|
46
|
+
execute: true,
|
|
47
|
+
evaluation: EvaluationOptions::offline(),
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
.unwrap();
|
|
51
|
+
|
|
52
|
+
assert_eq!(route.policy_action, "commit_task_baseline");
|
|
53
|
+
assert!(route.mutation_performed);
|
|
54
|
+
assert_eq!(repo.git_status_short(), "M USER.md");
|
|
55
|
+
|
|
56
|
+
let committed_paths = repo.git_stdout(&["show", "--name-only", "--format=", "HEAD"]);
|
|
57
|
+
assert!(committed_paths.contains("README.md"));
|
|
58
|
+
assert!(!committed_paths.contains("USER.md"));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#[test]
|
|
62
|
+
fn execute_route_journals_external_commit_after_completed_task() {
|
|
63
|
+
let repo = TestRepo::completed_task_with_diff("route-external-commit");
|
|
64
|
+
repo.git(&["add", "-A"]);
|
|
65
|
+
repo.git(&["commit", "-m", "manual baseline"]);
|
|
66
|
+
assert!(repo.git_status_short().is_empty());
|
|
67
|
+
|
|
68
|
+
let route = evaluate_route(
|
|
69
|
+
repo.path(),
|
|
70
|
+
"Create a new task for README polish.",
|
|
71
|
+
RouteOptions {
|
|
72
|
+
execute: true,
|
|
73
|
+
evaluation: EvaluationOptions::offline(),
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
.unwrap();
|
|
77
|
+
|
|
78
|
+
assert_eq!(route.repo_state_before, "ready_for_task");
|
|
79
|
+
assert!(route.mutation_performed);
|
|
80
|
+
assert!(route.can_create_task);
|
|
81
|
+
assert!(route
|
|
82
|
+
.executed_actions
|
|
83
|
+
.contains(&"journal_external_task_baseline".to_string()));
|
|
84
|
+
|
|
85
|
+
let journal = fs::read_to_string(repo.path().join(".naome/task-journal.jsonl")).unwrap();
|
|
86
|
+
assert!(journal.contains("\"outcome\":\"external_baseline\""));
|
|
87
|
+
assert!(journal.contains("\"taskId\":\"readme-task\""));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[test]
|
|
91
|
+
fn explain_reports_winning_rule_and_mutation_plan_without_executing() {
|
|
92
|
+
let repo = TestRepo::completed_task_with_diff("route-explain");
|
|
93
|
+
let before = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
94
|
+
|
|
95
|
+
let explain = explain_route(
|
|
96
|
+
repo.path(),
|
|
97
|
+
"Start a new task for README polish.",
|
|
98
|
+
EvaluationOptions::offline(),
|
|
99
|
+
)
|
|
100
|
+
.unwrap();
|
|
101
|
+
|
|
102
|
+
assert_eq!(
|
|
103
|
+
explain.winning_rule,
|
|
104
|
+
"completed_task_valid_new_task_auto_baseline"
|
|
105
|
+
);
|
|
106
|
+
assert!(explain.would_mutate);
|
|
107
|
+
assert!(explain
|
|
108
|
+
.discarded_candidate_actions
|
|
109
|
+
.contains(&"review_task_diff".to_string()));
|
|
110
|
+
assert!(explain
|
|
111
|
+
.required_context
|
|
112
|
+
.contains(&"docs/naome/execution.md".to_string()));
|
|
113
|
+
assert!(!explain.user_message.contains("commit_task_baseline"));
|
|
114
|
+
assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#[test]
|
|
118
|
+
fn unhealthy_harness_route_blocks_normal_work() {
|
|
119
|
+
let repo = TestRepo::new("route-unhealthy");
|
|
120
|
+
repo.init_git();
|
|
121
|
+
repo.write_file("README.md", "# Baseline\n");
|
|
122
|
+
repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
|
|
123
|
+
repo.write_file(".naome/bin/check-harness-health.js", "process.exit(1);\n");
|
|
124
|
+
repo.git(&["add", "."]);
|
|
125
|
+
repo.git(&["commit", "-m", "baseline"]);
|
|
126
|
+
|
|
127
|
+
let route = evaluate_route(
|
|
128
|
+
repo.path(),
|
|
129
|
+
"Create a new task.",
|
|
130
|
+
RouteOptions {
|
|
131
|
+
execute: true,
|
|
132
|
+
evaluation: EvaluationOptions::online(),
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
.unwrap();
|
|
136
|
+
|
|
137
|
+
assert_eq!(route.repo_state_before, "harness_unhealthy");
|
|
138
|
+
assert!(!route.mutation_performed);
|
|
139
|
+
assert!(!route.can_create_task);
|
|
140
|
+
assert!(!route.human_options.is_empty());
|
|
141
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
|
|
3
|
+
use naome_core::{evaluate_route, EvaluationOptions, RouteOptions};
|
|
4
|
+
|
|
5
|
+
mod repo_support;
|
|
6
|
+
|
|
7
|
+
use repo_support::{
|
|
8
|
+
assert_commit_paths, assert_isolated_worktree_ready, route_new_task, route_readme_task,
|
|
9
|
+
TestRepo,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
#[test]
|
|
13
|
+
fn execute_route_baselines_harness_refresh_before_dirty_repo_worktree() {
|
|
14
|
+
let repo = TestRepo::dirty_harness_refresh_repo("route-dirty-harness-refresh-worktree", true);
|
|
15
|
+
|
|
16
|
+
let route = route_readme_task(&repo, true);
|
|
17
|
+
|
|
18
|
+
assert_eq!(
|
|
19
|
+
route.policy_action,
|
|
20
|
+
"auto_commit_harness_refresh_then_create_isolated_task_worktree"
|
|
21
|
+
);
|
|
22
|
+
assert_eq!(
|
|
23
|
+
route.executed_actions,
|
|
24
|
+
vec![
|
|
25
|
+
"commit_harness_refresh_baseline".to_string(),
|
|
26
|
+
"create_task_worktree".to_string()
|
|
27
|
+
]
|
|
28
|
+
);
|
|
29
|
+
assert_isolated_worktree_ready(&route, &repo, "M USER.md");
|
|
30
|
+
assert_commit_paths(&repo, &["AGENTS.md", ".naome/manifest.json"], &["USER.md"]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[test]
|
|
34
|
+
fn execute_route_baselines_pure_harness_refresh_before_new_task_without_worktree() {
|
|
35
|
+
let repo = TestRepo::completed_task_with_harness_refresh_only(
|
|
36
|
+
"route-pure-harness-refresh-no-worktree",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
let route = route_readme_task(&repo, true);
|
|
40
|
+
|
|
41
|
+
assert_eq!(
|
|
42
|
+
route.policy_action,
|
|
43
|
+
"auto_commit_harness_refresh_then_create_new_task"
|
|
44
|
+
);
|
|
45
|
+
assert_eq!(
|
|
46
|
+
route.executed_actions,
|
|
47
|
+
vec!["commit_harness_refresh_baseline".to_string()]
|
|
48
|
+
);
|
|
49
|
+
assert!(route.mutation_performed);
|
|
50
|
+
assert!(route.can_create_task);
|
|
51
|
+
assert!(route.worktree.is_none());
|
|
52
|
+
assert_eq!(route.task_root, repo.path().to_string_lossy());
|
|
53
|
+
assert_eq!(route.next_decision.state, "ready_for_task");
|
|
54
|
+
assert_eq!(repo.git_status_short(), "");
|
|
55
|
+
|
|
56
|
+
assert_commit_paths(
|
|
57
|
+
&repo,
|
|
58
|
+
&["AGENTS.md", ".naome/manifest.json"],
|
|
59
|
+
&["README.md"],
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#[test]
|
|
64
|
+
fn execute_route_repair_request_baselines_harness_refresh_only() {
|
|
65
|
+
let repo = TestRepo::dirty_harness_refresh_repo("route-harness-refresh-repair-only", true);
|
|
66
|
+
|
|
67
|
+
let route = evaluate_route(
|
|
68
|
+
repo.path(),
|
|
69
|
+
"please repair all",
|
|
70
|
+
RouteOptions {
|
|
71
|
+
execute: true,
|
|
72
|
+
evaluation: EvaluationOptions::offline(),
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
.unwrap();
|
|
76
|
+
|
|
77
|
+
assert_eq!(route.policy_action, "auto_commit_harness_refresh_baseline");
|
|
78
|
+
assert!(route.mutation_performed);
|
|
79
|
+
assert_eq!(
|
|
80
|
+
route.executed_actions,
|
|
81
|
+
vec!["commit_harness_refresh_baseline".to_string()]
|
|
82
|
+
);
|
|
83
|
+
assert_eq!(repo.git_status_short(), "M USER.md");
|
|
84
|
+
|
|
85
|
+
assert_commit_paths(&repo, &["AGENTS.md", ".naome/manifest.json"], &["USER.md"]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#[test]
|
|
89
|
+
fn dry_route_plans_harness_refresh_split_before_completed_task_baseline() {
|
|
90
|
+
let repo = TestRepo::completed_task_with_harness_refresh_diff("route-dry-harness-refresh");
|
|
91
|
+
let before = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
92
|
+
|
|
93
|
+
let route = route_new_task(&repo, "Start a new task for README polish.", false);
|
|
94
|
+
|
|
95
|
+
assert_eq!(
|
|
96
|
+
route.policy_action,
|
|
97
|
+
"auto_commit_harness_refresh_then_completed_task_then_create_new_task"
|
|
98
|
+
);
|
|
99
|
+
assert!(!route.mutation_performed);
|
|
100
|
+
assert!(!route.can_create_task);
|
|
101
|
+
assert!(route.human_options.is_empty());
|
|
102
|
+
assert!(route.intent.risk_codes.is_empty());
|
|
103
|
+
assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#[test]
|
|
107
|
+
fn execute_route_splits_harness_refresh_then_completed_task_baseline() {
|
|
108
|
+
let repo = TestRepo::completed_task_with_harness_refresh_diff("route-execute-harness-refresh");
|
|
109
|
+
|
|
110
|
+
let route = route_new_task(&repo, "Start a new task for README polish.", true);
|
|
111
|
+
|
|
112
|
+
assert_eq!(
|
|
113
|
+
route.policy_action,
|
|
114
|
+
"auto_commit_harness_refresh_then_completed_task_then_create_new_task"
|
|
115
|
+
);
|
|
116
|
+
assert!(route.mutation_performed);
|
|
117
|
+
assert!(route.can_create_task);
|
|
118
|
+
assert_eq!(
|
|
119
|
+
route.executed_actions,
|
|
120
|
+
vec![
|
|
121
|
+
"commit_harness_refresh_baseline".to_string(),
|
|
122
|
+
"commit_task_baseline".to_string()
|
|
123
|
+
]
|
|
124
|
+
);
|
|
125
|
+
assert_eq!(route.next_decision.state, "ready_for_task");
|
|
126
|
+
assert!(repo.git_status_short().is_empty());
|
|
127
|
+
|
|
128
|
+
let log = repo.git_stdout(&["log", "--format=%s", "-2"]);
|
|
129
|
+
assert!(log.contains("chore(naome): baseline completed task"));
|
|
130
|
+
assert!(log.contains("chore(naome): baseline harness refresh"));
|
|
131
|
+
|
|
132
|
+
let journal = fs::read_to_string(repo.path().join(".naome/task-journal.jsonl")).unwrap();
|
|
133
|
+
assert!(journal.contains("\"taskId\":\"readme-task\""));
|
|
134
|
+
assert!(journal.contains("\"outcome\":\"route_auto_baseline\""));
|
|
135
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
|
|
3
|
+
use naome_core::{evaluate_route, EvaluationOptions, RouteOptions};
|
|
4
|
+
use serde_json::json;
|
|
5
|
+
|
|
6
|
+
mod repo_support;
|
|
7
|
+
|
|
8
|
+
use repo_support::{assert_user_diff_committed, route_commit_request, TestRepo};
|
|
9
|
+
|
|
10
|
+
#[test]
|
|
11
|
+
fn execute_route_does_not_mutate_or_offer_clear_commit_for_dirty_unowned_diff() {
|
|
12
|
+
let repo = TestRepo::idle_readme("route-dirty-commit-request");
|
|
13
|
+
repo.write_file("README.md", "# Manual edit\n");
|
|
14
|
+
let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
15
|
+
let before_status = repo.git_status_short();
|
|
16
|
+
|
|
17
|
+
let route = evaluate_route(
|
|
18
|
+
repo.path(),
|
|
19
|
+
"clear_or_commit_unowned_diff",
|
|
20
|
+
RouteOptions {
|
|
21
|
+
execute: true,
|
|
22
|
+
evaluation: EvaluationOptions::offline(),
|
|
23
|
+
},
|
|
24
|
+
)
|
|
25
|
+
.unwrap();
|
|
26
|
+
|
|
27
|
+
assert_eq!(route.policy_action, "block_unowned_diff");
|
|
28
|
+
assert!(!route.mutation_performed);
|
|
29
|
+
assert!(!route.can_create_task);
|
|
30
|
+
assert_eq!(route.executed_actions, Vec::<String>::new());
|
|
31
|
+
assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
|
|
32
|
+
assert_eq!(repo.git_status_short(), before_status);
|
|
33
|
+
assert!(!route
|
|
34
|
+
.human_options
|
|
35
|
+
.contains(&"clear_or_commit_unowned_diff".to_string()));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#[test]
|
|
39
|
+
fn execute_route_commits_user_diff_after_quality_gate_passes() {
|
|
40
|
+
let repo = TestRepo::readme_quality_repo(
|
|
41
|
+
"route-user-diff-quality-pass",
|
|
42
|
+
Vec::new(),
|
|
43
|
+
vec!["diff-check"],
|
|
44
|
+
);
|
|
45
|
+
repo.write_file("README.md", "# Manual edit\n");
|
|
46
|
+
|
|
47
|
+
let route = route_commit_request(&repo);
|
|
48
|
+
|
|
49
|
+
assert_user_diff_committed(&route, &repo);
|
|
50
|
+
|
|
51
|
+
let committed_paths = repo.git_stdout(&["show", "--name-only", "--format=", "HEAD"]);
|
|
52
|
+
assert!(committed_paths.contains("README.md"));
|
|
53
|
+
assert!(route.user_message.contains("quality gates passed"));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[test]
|
|
57
|
+
fn execute_route_runs_repository_quality_check_without_shelling_to_repo_command() {
|
|
58
|
+
let repo = TestRepo::repository_quality_failure_repo("route-repository-quality-check");
|
|
59
|
+
repo.write_file(
|
|
60
|
+
"src/large.js",
|
|
61
|
+
"export function legacy() {\n one();\n two();\n three();\n four();\n five();\n}\n",
|
|
62
|
+
);
|
|
63
|
+
let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
64
|
+
|
|
65
|
+
let route = route_commit_request(&repo);
|
|
66
|
+
|
|
67
|
+
assert_quality_gate_blocked(&repo, &route, before_head, "src/large.js");
|
|
68
|
+
assert!(route.user_message.contains("repository-quality-check"));
|
|
69
|
+
assert!(route.user_message.contains("file-length"));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[test]
|
|
73
|
+
fn execute_route_commits_product_diff_with_declared_safe_quality_checks() {
|
|
74
|
+
let repo = TestRepo::product_quality_repo("route-product-diff-quality-pass");
|
|
75
|
+
repo.write_file(
|
|
76
|
+
"packages/naome/crates/naome-core/src/route.rs",
|
|
77
|
+
"pub fn changed() {}\n",
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
let route = route_commit_request(&repo);
|
|
81
|
+
|
|
82
|
+
assert_user_diff_committed(&route, &repo);
|
|
83
|
+
|
|
84
|
+
let committed_paths = repo.git_stdout(&["show", "--name-only", "--format=", "HEAD"]);
|
|
85
|
+
assert!(committed_paths.contains("packages/naome/crates/naome-core/src/route.rs"));
|
|
86
|
+
assert!(route.user_message.contains("quality gates passed"));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[test]
|
|
90
|
+
fn execute_route_preserves_staged_rename_when_committing_user_diff() {
|
|
91
|
+
let repo = TestRepo::new("route-user-diff-staged-rename");
|
|
92
|
+
repo.init_git();
|
|
93
|
+
repo.write_file("README.md", "# Baseline\n");
|
|
94
|
+
repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
|
|
95
|
+
repo.write_readme_rename_verification();
|
|
96
|
+
repo.commit_all("baseline");
|
|
97
|
+
repo.git(&["mv", "README.md", "INTRO.md"]);
|
|
98
|
+
|
|
99
|
+
let route = route_commit_request(&repo);
|
|
100
|
+
|
|
101
|
+
assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
|
|
102
|
+
assert!(route.allowed);
|
|
103
|
+
assert!(route.mutation_performed);
|
|
104
|
+
assert_eq!(repo.git_status_short(), "");
|
|
105
|
+
|
|
106
|
+
let committed_paths = repo.git_stdout(&["show", "--name-status", "--format=", "HEAD"]);
|
|
107
|
+
assert!(committed_paths.contains("README.md"));
|
|
108
|
+
assert!(committed_paths.contains("INTRO.md"));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[test]
|
|
112
|
+
fn execute_route_refuses_user_diff_commit_without_quality_coverage() {
|
|
113
|
+
let repo = TestRepo::idle_readme("route-user-diff-no-quality-coverage");
|
|
114
|
+
repo.write_file("README.md", "# Manual edit\n");
|
|
115
|
+
let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
116
|
+
|
|
117
|
+
let route = route_commit_request(&repo);
|
|
118
|
+
|
|
119
|
+
assert_quality_gate_blocked(&repo, &route, before_head, "README.md");
|
|
120
|
+
assert!(route.user_message.contains("No quality coverage"));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#[test]
|
|
124
|
+
fn execute_route_refuses_user_diff_commit_when_check_mutates_after_diff_check() {
|
|
125
|
+
let repo = TestRepo::readme_quality_repo(
|
|
126
|
+
"route-user-diff-mutating-check",
|
|
127
|
+
vec![json!({
|
|
128
|
+
"id": "mutate-readme-after-check",
|
|
129
|
+
"command": "node -e \"require('fs').writeFileSync('README.md', '# Mutated \\\\n')\"",
|
|
130
|
+
"cwd": ".",
|
|
131
|
+
"purpose": "Simulate a mutating check that dirties checked content.",
|
|
132
|
+
"cost": "fast",
|
|
133
|
+
"source": "test",
|
|
134
|
+
"evidence": ["README.md"],
|
|
135
|
+
"lastVerified": null
|
|
136
|
+
})],
|
|
137
|
+
vec!["mutate-readme-after-check"],
|
|
138
|
+
);
|
|
139
|
+
repo.write_file("README.md", "# Manual edit\n");
|
|
140
|
+
let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
141
|
+
|
|
142
|
+
let route = route_commit_request(&repo);
|
|
143
|
+
|
|
144
|
+
assert_quality_gate_blocked(&repo, &route, before_head, "README.md");
|
|
145
|
+
assert_eq!(
|
|
146
|
+
fs::read_to_string(repo.path().join("README.md")).unwrap(),
|
|
147
|
+
"# Manual edit\n"
|
|
148
|
+
);
|
|
149
|
+
assert!(route
|
|
150
|
+
.user_message
|
|
151
|
+
.contains("will not execute repository-controlled verification commands"));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#[test]
|
|
155
|
+
fn execute_route_refuses_user_diff_commit_when_diff_check_command_is_unsafe() {
|
|
156
|
+
let repo = TestRepo::readme_unsafe_diff_check_repo("route-user-diff-mutating-diff-check");
|
|
157
|
+
repo.write_file("README.md", "# Manual edit\n");
|
|
158
|
+
let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
159
|
+
|
|
160
|
+
let route = route_commit_request(&repo);
|
|
161
|
+
|
|
162
|
+
assert_quality_gate_blocked(&repo, &route, before_head, "README.md");
|
|
163
|
+
assert!(!repo.path().join("NEW.md").exists());
|
|
164
|
+
assert!(route
|
|
165
|
+
.user_message
|
|
166
|
+
.contains("Quality check diff-check has an unsafe command or cwd"));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn execute_route_refuses_user_diff_commit_when_quality_gate_fails() {
|
|
171
|
+
let repo = TestRepo::readme_quality_repo(
|
|
172
|
+
"route-user-diff-quality-fail",
|
|
173
|
+
Vec::new(),
|
|
174
|
+
vec!["diff-check"],
|
|
175
|
+
);
|
|
176
|
+
repo.write_file("README.md", "# Manual edit \n");
|
|
177
|
+
let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
178
|
+
|
|
179
|
+
let route = route_commit_request(&repo);
|
|
180
|
+
|
|
181
|
+
assert_quality_gate_blocked(&repo, &route, before_head, "README.md");
|
|
182
|
+
assert_eq!(route.human_options, vec!["review_unowned_diff"]);
|
|
183
|
+
assert!(route.user_message.contains("quality gate failed"));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
fn assert_quality_gate_blocked(
|
|
187
|
+
repo: &TestRepo,
|
|
188
|
+
route: &naome_core::RouteDecision,
|
|
189
|
+
before_head: String,
|
|
190
|
+
changed_path: &str,
|
|
191
|
+
) {
|
|
192
|
+
assert_eq!(route.policy_action, "commit_user_diff_with_quality_gate");
|
|
193
|
+
assert!(!route.allowed);
|
|
194
|
+
assert!(!route.mutation_performed);
|
|
195
|
+
assert_eq!(route.executed_actions, vec!["run_user_diff_quality_gate"]);
|
|
196
|
+
assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
|
|
197
|
+
assert!(repo.git_status_short().contains(changed_path));
|
|
198
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
use serde_json::json;
|
|
2
|
+
|
|
3
|
+
mod repo_support;
|
|
4
|
+
|
|
5
|
+
use repo_support::{route_readme_task, try_route_readme_task, TestRepo};
|
|
6
|
+
|
|
7
|
+
#[test]
|
|
8
|
+
fn execute_route_refuses_to_create_more_than_max_isolated_worktrees() {
|
|
9
|
+
let repo = TestRepo::new("route-worktree-limit");
|
|
10
|
+
repo.init_git();
|
|
11
|
+
repo.write_file("README.md", "# Baseline\n");
|
|
12
|
+
repo.write_file("USER.md", "user baseline\n");
|
|
13
|
+
repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
|
|
14
|
+
repo.git(&["add", "."]);
|
|
15
|
+
repo.git(&["commit", "-m", "baseline"]);
|
|
16
|
+
repo.write_file("USER.md", "user local edit\n");
|
|
17
|
+
repo.create_stale_task_worktrees(25);
|
|
18
|
+
|
|
19
|
+
let error = try_route_readme_task(&repo, true).unwrap_err();
|
|
20
|
+
|
|
21
|
+
assert!(error
|
|
22
|
+
.to_string()
|
|
23
|
+
.contains("Too many NAOME task worktrees are present"));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#[test]
|
|
27
|
+
fn execute_route_preflights_worktree_before_completed_task_baseline() {
|
|
28
|
+
let repo =
|
|
29
|
+
TestRepo::completed_task_with_unrelated_user_edit("route-completed-worktree-preflight");
|
|
30
|
+
let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
31
|
+
let before_status = repo.git_status_short();
|
|
32
|
+
repo.create_stale_task_worktrees(25);
|
|
33
|
+
|
|
34
|
+
let error = try_route_readme_task(&repo, true).unwrap_err();
|
|
35
|
+
|
|
36
|
+
assert!(error
|
|
37
|
+
.to_string()
|
|
38
|
+
.contains("Too many NAOME task worktrees are present"));
|
|
39
|
+
assert_eq!(repo.git_stdout(&["rev-parse", "HEAD"]), before_head);
|
|
40
|
+
assert_eq!(repo.git_status_short(), before_status);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[test]
|
|
44
|
+
fn execute_route_uses_preflighted_worktree_name_after_completed_task_baseline() {
|
|
45
|
+
let repo = TestRepo::completed_task_with_unrelated_user_edit("route-worktree-name-preflight");
|
|
46
|
+
let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
47
|
+
let before_short = &before_head[..12];
|
|
48
|
+
|
|
49
|
+
let route = route_readme_task(&repo, true);
|
|
50
|
+
|
|
51
|
+
let worktree = route.worktree.expect("route should create a worktree");
|
|
52
|
+
assert!(worktree.branch.contains(before_short));
|
|
53
|
+
assert!(worktree.path.contains(before_short));
|
|
54
|
+
}
|