@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
package/bin/naome.js
CHANGED
|
@@ -12,7 +12,7 @@ const packageVersion = packageMetadata.version;
|
|
|
12
12
|
const nativeBinaryName = process.platform === "win32" ? "naome.exe" : "naome";
|
|
13
13
|
const args = process.argv.slice(2);
|
|
14
14
|
const [command] = args;
|
|
15
|
-
const helpCommands = "status [--json]|next [--json]|intent --prompt-file <path> [--json]|intent --prompt <text> [--json]|route --prompt-file <path> [--execute] [--json]|route --prompt <text> [--execute] [--json]|explain --prompt-file <path> [--json]|explain --prompt <text> [--json]|install|sync [--check-update]|update [--json] [--execute]|quality init [--json]|quality check --changed [--json]|quality report [--json]|cleanup plan [--json]|cleanup route --path <path> [--json]|refresh-integrity [--json]|workflow search-profile|check-search|phases|processes|mutations [--json]|commit -m \"type(scope): message\"".split("|");
|
|
15
|
+
const helpCommands = "status [--json]|next [--json]|intent --prompt-file <path> [--json]|intent --prompt <text> [--json]|route --prompt-file <path> [--execute] [--json]|route --prompt <text> [--execute] [--json]|explain --prompt-file <path> [--json]|explain --prompt <text> [--json]|install|sync [--check-update]|update [--json] [--execute]|quality init [--json]|quality check --changed [--json]|quality report [--json]|structure report [--json]|structure explain --path <path> [--json]|cleanup plan [--json]|cleanup route --path <path> [--json]|refresh-integrity [--json]|workflow search-profile|check-search|phases|processes|mutations [--json]|commit -m \"type(scope): message\"".split("|");
|
|
16
16
|
|
|
17
17
|
if (isHelpRequest(args)) {
|
|
18
18
|
printHelp();
|
|
@@ -265,13 +265,19 @@ function runNativePackageCommand(args) {
|
|
|
265
265
|
|
|
266
266
|
function ensureRepositoryQualityInitialized(nativeBinary, qualityConfigExisted) {
|
|
267
267
|
const root = process.cwd();
|
|
268
|
-
if (
|
|
268
|
+
if (!existsSync(join(root, ".naome"))) {
|
|
269
269
|
return;
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
if (qualityConfigExisted && repositoryQualitySupportFilesExist(root)) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!qualityConfigExisted) {
|
|
277
|
+
for (const path of repositoryQualityPaths(root)) {
|
|
278
|
+
if (existsSync(path)) {
|
|
279
|
+
unlinkSync(path);
|
|
280
|
+
}
|
|
275
281
|
}
|
|
276
282
|
}
|
|
277
283
|
|
|
@@ -303,10 +309,18 @@ function repositoryQualityConfigPath(root) {
|
|
|
303
309
|
function repositoryQualityPaths(root) {
|
|
304
310
|
return [
|
|
305
311
|
repositoryQualityConfigPath(root),
|
|
312
|
+
join(root, ".naome", "repository-structure.json"),
|
|
306
313
|
join(root, ".naome", "repository-quality-baseline.json")
|
|
307
314
|
];
|
|
308
315
|
}
|
|
309
316
|
|
|
317
|
+
function repositoryQualitySupportFilesExist(root) {
|
|
318
|
+
return [
|
|
319
|
+
join(root, ".naome", "repository-structure.json"),
|
|
320
|
+
join(root, ".naome", "repository-quality-baseline.json")
|
|
321
|
+
].every((path) => existsSync(path));
|
|
322
|
+
}
|
|
323
|
+
|
|
310
324
|
function resolveNativePackageBinary() {
|
|
311
325
|
const candidates = [
|
|
312
326
|
process.env.NAOME_NATIVE_BIN && resolve(process.cwd(), process.env.NAOME_NATIVE_BIN),
|
|
@@ -3,7 +3,7 @@ use std::path::Path;
|
|
|
3
3
|
use crate::check_commands::{run_harness_health, run_task_state, run_verification_contract};
|
|
4
4
|
use crate::install_bridge::run_install_bridge;
|
|
5
5
|
use crate::prompt_commands::{run_explain, run_intent, run_route};
|
|
6
|
-
use crate::quality_commands::{run_cleanup_command, run_quality_command};
|
|
6
|
+
use crate::quality_commands::{run_cleanup_command, run_quality_command, run_structure_command};
|
|
7
7
|
use crate::simple_commands::{
|
|
8
8
|
print_install_plan, run_commit_paths, run_journal_task, seed_verification,
|
|
9
9
|
};
|
|
@@ -20,6 +20,7 @@ pub fn dispatch_command(
|
|
|
20
20
|
"seed-verification" => seed_verification(root)?,
|
|
21
21
|
"refresh-integrity" => run_refresh_integrity(root, args)?,
|
|
22
22
|
"quality" => run_quality_command(root, args)?,
|
|
23
|
+
"structure" => run_structure_command(root, args)?,
|
|
23
24
|
"cleanup" => run_cleanup_command(root, args)?,
|
|
24
25
|
"workflow" => run_workflow_command(root, args)?,
|
|
25
26
|
"check-harness-health" => run_harness_health(root, args)?,
|
|
@@ -30,6 +30,8 @@ const HELP: &str = r#"Usage:
|
|
|
30
30
|
naome quality init [--json]
|
|
31
31
|
naome quality check --changed [--json]
|
|
32
32
|
naome quality report [--json]
|
|
33
|
+
naome structure report [--json]
|
|
34
|
+
naome structure explain --path <path> [--json]
|
|
33
35
|
naome cleanup plan [--json]
|
|
34
36
|
naome cleanup route --path <path> [--json]
|
|
35
37
|
naome workflow search-profile [--json]
|
|
@@ -71,6 +73,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
71
73
|
&& command != "refresh-integrity"
|
|
72
74
|
&& command != "workflow"
|
|
73
75
|
&& command != "quality"
|
|
76
|
+
&& command != "structure"
|
|
74
77
|
&& command != "cleanup"
|
|
75
78
|
&& command != "install-plan"
|
|
76
79
|
&& command != "install"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
use std::path::Path;
|
|
2
2
|
|
|
3
3
|
use naome_core::{
|
|
4
|
-
check_repository_quality,
|
|
5
|
-
QualityMode,
|
|
4
|
+
check_repository_quality, explain_repository_structure, init_repository_quality,
|
|
5
|
+
plan_quality_cleanup, route_quality_cleanup, QualityMode,
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
use crate::cli_args::option_value;
|
|
@@ -44,6 +44,23 @@ pub fn run_cleanup_command(root: &Path, args: &[String]) -> Result<(), Box<dyn s
|
|
|
44
44
|
Ok(())
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
pub fn run_structure_command(
|
|
48
|
+
root: &Path,
|
|
49
|
+
args: &[String],
|
|
50
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
51
|
+
let Some(subcommand) = args.get(1).map(String::as_str) else {
|
|
52
|
+
return Err("naome structure requires report or explain.".into());
|
|
53
|
+
};
|
|
54
|
+
let json = args.iter().any(|arg| arg == "--json");
|
|
55
|
+
|
|
56
|
+
match subcommand {
|
|
57
|
+
"report" => run_structure_report(root, json)?,
|
|
58
|
+
"explain" => run_structure_explain(root, args, json)?,
|
|
59
|
+
_ => return Err(format!("unknown naome structure command: {subcommand}").into()),
|
|
60
|
+
}
|
|
61
|
+
Ok(())
|
|
62
|
+
}
|
|
63
|
+
|
|
47
64
|
fn run_quality_check(
|
|
48
65
|
root: &Path,
|
|
49
66
|
args: &[String],
|
|
@@ -132,6 +149,77 @@ fn run_cleanup_route(
|
|
|
132
149
|
Ok(())
|
|
133
150
|
}
|
|
134
151
|
|
|
152
|
+
fn run_structure_report(root: &Path, json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|
153
|
+
let mut report = check_repository_quality(root, QualityMode::Report)?;
|
|
154
|
+
report
|
|
155
|
+
.violations
|
|
156
|
+
.retain(|violation| is_structure_check(&violation.check_id));
|
|
157
|
+
report.summary.violation_count = report.violations.len();
|
|
158
|
+
report.summary.blocking_violation_count = report.violations.len();
|
|
159
|
+
report.summary.baseline_violation_count = report
|
|
160
|
+
.violations
|
|
161
|
+
.iter()
|
|
162
|
+
.filter(|violation| violation.baseline)
|
|
163
|
+
.count();
|
|
164
|
+
report.ok = report.violations.is_empty();
|
|
165
|
+
report.schema = "naome.repository-structure-report.v1".to_string();
|
|
166
|
+
|
|
167
|
+
if json {
|
|
168
|
+
println!("{}", serde_json::to_string_pretty(&report)?);
|
|
169
|
+
} else if report.violations.is_empty() {
|
|
170
|
+
println!("NAOME repository structure report: no debt found.");
|
|
171
|
+
} else {
|
|
172
|
+
println!(
|
|
173
|
+
"NAOME repository structure report: {} violation(s).",
|
|
174
|
+
report.violations.len()
|
|
175
|
+
);
|
|
176
|
+
for violation in report.violations.iter().take(20) {
|
|
177
|
+
print_quality_violation(violation);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
Ok(())
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fn run_structure_explain(
|
|
184
|
+
root: &Path,
|
|
185
|
+
args: &[String],
|
|
186
|
+
json: bool,
|
|
187
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
188
|
+
let Some(path) = option_value(args, "--path") else {
|
|
189
|
+
return Err("naome structure explain requires --path <path>.".into());
|
|
190
|
+
};
|
|
191
|
+
let explanation = explain_repository_structure(root, path)?;
|
|
192
|
+
if json {
|
|
193
|
+
println!("{}", serde_json::to_string_pretty(&explanation)?);
|
|
194
|
+
} else {
|
|
195
|
+
println!(
|
|
196
|
+
"{} role={} layer={} directory={}",
|
|
197
|
+
explanation.path, explanation.role, explanation.layer, explanation.directory
|
|
198
|
+
);
|
|
199
|
+
if let Some(language) = explanation.language {
|
|
200
|
+
println!("language={language}");
|
|
201
|
+
}
|
|
202
|
+
if let Some(module) = explanation.module {
|
|
203
|
+
println!("module={module}");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
Ok(())
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fn is_structure_check(check_id: &str) -> bool {
|
|
210
|
+
matches!(
|
|
211
|
+
check_id,
|
|
212
|
+
"directory-role-mixing"
|
|
213
|
+
| "misplaced-file-role"
|
|
214
|
+
| "root-file-sprawl"
|
|
215
|
+
| "dumping-ground-directory"
|
|
216
|
+
| "directory-size"
|
|
217
|
+
| "path-depth"
|
|
218
|
+
| "case-collision"
|
|
219
|
+
| "test-source-pairing"
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
135
223
|
fn print_quality_violation(violation: &naome_core::QualityViolation) {
|
|
136
224
|
let location = violation
|
|
137
225
|
.line
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
use std::ffi::OsString;
|
|
2
|
+
use std::path::Path;
|
|
3
|
+
use std::process::Command;
|
|
4
|
+
|
|
5
|
+
use crate::models::{CheckDecision, NaomeError};
|
|
6
|
+
|
|
7
|
+
pub(super) fn run_node_check(
|
|
8
|
+
root: &Path,
|
|
9
|
+
script: &str,
|
|
10
|
+
args: &[&str],
|
|
11
|
+
) -> Result<CheckDecision, NaomeError> {
|
|
12
|
+
let mut command_args = vec![script.to_string()];
|
|
13
|
+
command_args.extend(args.iter().map(ToString::to_string));
|
|
14
|
+
let node_bin = std::env::var_os("NAOME_NODE_BIN").unwrap_or_else(|| OsString::from("node"));
|
|
15
|
+
let output = Command::new(&node_bin)
|
|
16
|
+
.args(&command_args)
|
|
17
|
+
.current_dir(root)
|
|
18
|
+
.output()?;
|
|
19
|
+
let mut combined = String::new();
|
|
20
|
+
combined.push_str(&String::from_utf8_lossy(&output.stdout));
|
|
21
|
+
combined.push_str(&String::from_utf8_lossy(&output.stderr));
|
|
22
|
+
|
|
23
|
+
Ok(CheckDecision {
|
|
24
|
+
command: format!(
|
|
25
|
+
"{} {}{}",
|
|
26
|
+
node_bin.to_string_lossy(),
|
|
27
|
+
script,
|
|
28
|
+
if args.is_empty() {
|
|
29
|
+
String::new()
|
|
30
|
+
} else {
|
|
31
|
+
format!(" {}", args.join(" "))
|
|
32
|
+
}
|
|
33
|
+
),
|
|
34
|
+
exit_code: output.status.code(),
|
|
35
|
+
ok: output.status.success(),
|
|
36
|
+
output: combined.trim().to_string(),
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub(super) fn extract_known_actions(output: &str) -> Vec<&'static str> {
|
|
41
|
+
const KNOWN: [&str; 11] = [
|
|
42
|
+
"commit_task_baseline",
|
|
43
|
+
"review_task_diff",
|
|
44
|
+
"request_task_changes",
|
|
45
|
+
"cancel_task_changes",
|
|
46
|
+
"commit_upgrade_baseline",
|
|
47
|
+
"review_diff_first",
|
|
48
|
+
"cancel_upgrade_baseline",
|
|
49
|
+
"run_first_run_protocol",
|
|
50
|
+
"run_upgrade_protocol",
|
|
51
|
+
"review_unowned_diff",
|
|
52
|
+
"create_task",
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
let actions: Vec<&'static str> = KNOWN
|
|
56
|
+
.into_iter()
|
|
57
|
+
.filter(|action| output.contains(action))
|
|
58
|
+
.collect();
|
|
59
|
+
if actions.is_empty() {
|
|
60
|
+
vec!["review_task_admission"]
|
|
61
|
+
} else {
|
|
62
|
+
actions
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use crate::models::{Decision, NaomeError};
|
|
4
|
+
use crate::paths;
|
|
5
|
+
use crate::task_state::harness_refresh_diff;
|
|
6
|
+
|
|
7
|
+
pub(super) fn idle_diff_decision(
|
|
8
|
+
root: &Path,
|
|
9
|
+
changed_paths: &[String],
|
|
10
|
+
machine_owned: &[String],
|
|
11
|
+
known_harness_paths: &[String],
|
|
12
|
+
) -> Result<Decision, NaomeError> {
|
|
13
|
+
let all_machine_owned = !machine_owned.is_empty()
|
|
14
|
+
&& changed_paths
|
|
15
|
+
.iter()
|
|
16
|
+
.all(|path| paths::matches_any(path, machine_owned));
|
|
17
|
+
let all_harness_owned = !known_harness_paths.is_empty()
|
|
18
|
+
&& changed_paths
|
|
19
|
+
.iter()
|
|
20
|
+
.all(|path| paths::matches_any(path, known_harness_paths));
|
|
21
|
+
|
|
22
|
+
if harness_refresh_diff(root)?.is_some_and(|diff| diff.unrelated_paths.is_empty()) {
|
|
23
|
+
Ok(harness_repair_decision(
|
|
24
|
+
"Machine-owned NAOME harness refresh files changed outside an active task.",
|
|
25
|
+
"Run NAOME intent for the next natural-language request; deterministic policy can baseline a pure harness refresh automatically.",
|
|
26
|
+
))
|
|
27
|
+
} else if all_machine_owned {
|
|
28
|
+
Ok(harness_repair_decision(
|
|
29
|
+
"Machine-owned NAOME harness files changed outside an active task.",
|
|
30
|
+
"Review and baseline the harness repair or cancel it before feature work.",
|
|
31
|
+
))
|
|
32
|
+
} else if all_harness_owned {
|
|
33
|
+
Ok(Decision::new(
|
|
34
|
+
"install_or_upgrade_unbaselined",
|
|
35
|
+
true,
|
|
36
|
+
"NAOME setup or upgrade files changed outside an active task.",
|
|
37
|
+
vec![
|
|
38
|
+
"commit_upgrade_baseline",
|
|
39
|
+
"review_diff_first",
|
|
40
|
+
"cancel_upgrade_baseline",
|
|
41
|
+
],
|
|
42
|
+
"Resolve the setup or upgrade diff before feature work.",
|
|
43
|
+
))
|
|
44
|
+
} else {
|
|
45
|
+
Ok(Decision::new(
|
|
46
|
+
"dirty_unowned_diff",
|
|
47
|
+
true,
|
|
48
|
+
"The repository has changes not owned by an active NAOME task.",
|
|
49
|
+
vec!["review_unowned_diff"],
|
|
50
|
+
"Review the unowned diff, or route a new task so NAOME can isolate task work without touching it.",
|
|
51
|
+
))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fn harness_repair_decision(user_message: &str, next_action: &str) -> Decision {
|
|
56
|
+
Decision::new(
|
|
57
|
+
"harness_repair_unbaselined",
|
|
58
|
+
true,
|
|
59
|
+
user_message,
|
|
60
|
+
vec![
|
|
61
|
+
"commit_upgrade_baseline",
|
|
62
|
+
"review_diff_first",
|
|
63
|
+
"cancel_upgrade_baseline",
|
|
64
|
+
],
|
|
65
|
+
next_action,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
use std::path::Path;
|
|
3
|
+
|
|
4
|
+
use serde_json::Value;
|
|
5
|
+
|
|
6
|
+
use crate::models::NaomeError;
|
|
7
|
+
|
|
8
|
+
pub(super) fn read_json(root: &Path, relative_path: &str) -> Result<Value, NaomeError> {
|
|
9
|
+
let content = fs::read_to_string(root.join(relative_path))?;
|
|
10
|
+
Ok(serde_json::from_str(&content)?)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pub(super) fn json_bool(value: &Value, key: &str) -> Option<bool> {
|
|
14
|
+
value.get(key).and_then(Value::as_bool)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
pub(super) fn json_string(value: &Value, key: &str) -> Option<String> {
|
|
18
|
+
value
|
|
19
|
+
.get(key)
|
|
20
|
+
.and_then(Value::as_str)
|
|
21
|
+
.map(ToString::to_string)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub(super) fn string_array_at(value: &Value, key: &str) -> Vec<String> {
|
|
25
|
+
value
|
|
26
|
+
.get(key)
|
|
27
|
+
.and_then(Value::as_array)
|
|
28
|
+
.map(|values| {
|
|
29
|
+
values
|
|
30
|
+
.iter()
|
|
31
|
+
.filter_map(Value::as_str)
|
|
32
|
+
.map(ToString::to_string)
|
|
33
|
+
.collect()
|
|
34
|
+
})
|
|
35
|
+
.unwrap_or_default()
|
|
36
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use serde_json::Value;
|
|
4
|
+
|
|
5
|
+
use crate::models::{Decision, NaomeError, TaskDecision};
|
|
6
|
+
use crate::paths;
|
|
7
|
+
use crate::task_state::harness_refresh_diff;
|
|
8
|
+
|
|
9
|
+
use super::idle::idle_diff_decision;
|
|
10
|
+
use super::json::{json_string, read_json, string_array_at};
|
|
11
|
+
|
|
12
|
+
pub(super) fn completed_task_decision(
|
|
13
|
+
root: &Path,
|
|
14
|
+
changed_paths: Vec<String>,
|
|
15
|
+
task: Option<&TaskDecision>,
|
|
16
|
+
) -> Result<Decision, NaomeError> {
|
|
17
|
+
let mut decision = if changed_paths.is_empty() {
|
|
18
|
+
Decision::new(
|
|
19
|
+
"ready_for_task",
|
|
20
|
+
false,
|
|
21
|
+
"The last completed NAOME task has no open diff.",
|
|
22
|
+
vec!["create_task"],
|
|
23
|
+
"Task admission is clear; create the next task before feature work.",
|
|
24
|
+
)
|
|
25
|
+
} else if has_completed_task_owned_paths(task, &changed_paths) {
|
|
26
|
+
Decision::new(
|
|
27
|
+
"completed_task_unbaselined",
|
|
28
|
+
true,
|
|
29
|
+
"A completed NAOME task is verified and waiting for the next routing decision.",
|
|
30
|
+
vec![
|
|
31
|
+
"commit_task_baseline",
|
|
32
|
+
"review_task_diff",
|
|
33
|
+
"request_task_changes",
|
|
34
|
+
"cancel_task_changes",
|
|
35
|
+
],
|
|
36
|
+
"Run NAOME intent for the next natural-language request; deterministic policy can baseline a valid completed task automatically.",
|
|
37
|
+
)
|
|
38
|
+
} else if harness_refresh_diff(root)?.is_some_and(|diff| diff.unrelated_paths.is_empty()) {
|
|
39
|
+
Decision::new(
|
|
40
|
+
"harness_repair_unbaselined",
|
|
41
|
+
true,
|
|
42
|
+
"Machine-owned NAOME harness refresh files changed outside an active task.",
|
|
43
|
+
vec![
|
|
44
|
+
"commit_upgrade_baseline",
|
|
45
|
+
"review_diff_first",
|
|
46
|
+
"cancel_upgrade_baseline",
|
|
47
|
+
],
|
|
48
|
+
"Run NAOME intent for the next natural-language request; deterministic policy can baseline a pure harness refresh automatically.",
|
|
49
|
+
)
|
|
50
|
+
} else {
|
|
51
|
+
Decision::new(
|
|
52
|
+
"dirty_unowned_diff",
|
|
53
|
+
true,
|
|
54
|
+
"The completed NAOME task has been baselined, but unrelated user changes remain.",
|
|
55
|
+
vec!["review_unowned_diff"],
|
|
56
|
+
"Review the unrelated diff, or route a new task so NAOME can isolate task work without touching it.",
|
|
57
|
+
)
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
decision.changed_paths = changed_paths;
|
|
61
|
+
decision.required_context = vec![
|
|
62
|
+
"docs/naome/execution.md".to_string(),
|
|
63
|
+
".naome/task-state.json".to_string(),
|
|
64
|
+
"docs/naome/agent-workflow.md".to_string(),
|
|
65
|
+
"docs/naome/testing.md".to_string(),
|
|
66
|
+
];
|
|
67
|
+
Ok(decision)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub(super) fn active_task_decision(
|
|
71
|
+
changed_paths: Vec<String>,
|
|
72
|
+
task: Option<&TaskDecision>,
|
|
73
|
+
) -> Decision {
|
|
74
|
+
let allowed_paths = task
|
|
75
|
+
.map(|task| task.allowed_paths.clone())
|
|
76
|
+
.unwrap_or_default();
|
|
77
|
+
let out_of_scope: Vec<String> = changed_paths
|
|
78
|
+
.iter()
|
|
79
|
+
.filter(|path| !is_control_state_path(path) && !paths::matches_any(path, &allowed_paths))
|
|
80
|
+
.cloned()
|
|
81
|
+
.collect();
|
|
82
|
+
|
|
83
|
+
let mut decision = if out_of_scope.is_empty() {
|
|
84
|
+
Decision::new(
|
|
85
|
+
"active_task_in_progress",
|
|
86
|
+
false,
|
|
87
|
+
"A NAOME task is active and the current diff is inside its declared scope.",
|
|
88
|
+
vec!["continue_task", "request_task_changes", "complete_task"],
|
|
89
|
+
"Continue the active task and keep proof current.",
|
|
90
|
+
)
|
|
91
|
+
} else {
|
|
92
|
+
Decision::new(
|
|
93
|
+
"active_task_blocked",
|
|
94
|
+
true,
|
|
95
|
+
"A NAOME task is active, but the current diff includes paths outside its declared scope.",
|
|
96
|
+
vec![
|
|
97
|
+
"revise_task_scope",
|
|
98
|
+
"revert_out_of_scope_diff",
|
|
99
|
+
"request_human_review",
|
|
100
|
+
],
|
|
101
|
+
"Resolve out-of-scope changes before completing this task.",
|
|
102
|
+
)
|
|
103
|
+
};
|
|
104
|
+
decision.changed_paths = if out_of_scope.is_empty() {
|
|
105
|
+
changed_paths
|
|
106
|
+
} else {
|
|
107
|
+
out_of_scope
|
|
108
|
+
};
|
|
109
|
+
decision.required_context = vec![
|
|
110
|
+
"docs/naome/execution.md".to_string(),
|
|
111
|
+
".naome/task-state.json".to_string(),
|
|
112
|
+
"docs/naome/testing.md".to_string(),
|
|
113
|
+
];
|
|
114
|
+
decision
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
pub(super) fn classify_idle_diff(
|
|
118
|
+
root: &Path,
|
|
119
|
+
changed_paths: Vec<String>,
|
|
120
|
+
) -> Result<Decision, NaomeError> {
|
|
121
|
+
let manifest = read_json(root, ".naome/manifest.json").unwrap_or(Value::Null);
|
|
122
|
+
let machine_owned = string_array_at(&manifest, "machineOwned");
|
|
123
|
+
let project_owned = string_array_at(&manifest, "projectOwned");
|
|
124
|
+
|
|
125
|
+
let mut known_harness_paths = machine_owned.clone();
|
|
126
|
+
known_harness_paths.extend(project_owned);
|
|
127
|
+
let mut decision =
|
|
128
|
+
idle_diff_decision(root, &changed_paths, &machine_owned, &known_harness_paths)?;
|
|
129
|
+
|
|
130
|
+
decision.changed_paths = changed_paths;
|
|
131
|
+
decision.required_context = vec![
|
|
132
|
+
"docs/naome/execution.md".to_string(),
|
|
133
|
+
".naome/task-state.json".to_string(),
|
|
134
|
+
];
|
|
135
|
+
Ok(decision)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
pub(super) fn task_decision(task_state: &Value, status: &str) -> Option<TaskDecision> {
|
|
139
|
+
let active_task = task_state.get("activeTask")?;
|
|
140
|
+
if active_task.is_null() {
|
|
141
|
+
return None;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Some(TaskDecision {
|
|
145
|
+
id: json_string(active_task, "id"),
|
|
146
|
+
status: status.to_string(),
|
|
147
|
+
request: json_string(active_task, "request"),
|
|
148
|
+
allowed_paths: string_array_at(active_task, "allowedPaths"),
|
|
149
|
+
required_check_ids: string_array_at(active_task, "requiredCheckIds"),
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fn has_completed_task_owned_paths(task: Option<&TaskDecision>, changed_paths: &[String]) -> bool {
|
|
154
|
+
let Some(task) = task else {
|
|
155
|
+
return false;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
changed_paths
|
|
159
|
+
.iter()
|
|
160
|
+
.any(|path| is_control_state_path(path) || paths::matches_any(path, &task.allowed_paths))
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn is_control_state_path(path: &str) -> bool {
|
|
164
|
+
path == ".naome/task-state.json"
|
|
165
|
+
}
|