@lamentis/naome 1.3.0 → 1.3.2
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/README.md +11 -2
- package/bin/naome.js +62 -24
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/context_commands.rs +47 -0
- package/crates/naome-cli/src/dispatcher.rs +6 -0
- package/crates/naome-cli/src/main.rs +43 -6
- package/crates/naome-cli/src/quality_commands.rs +31 -46
- package/crates/naome-cli/src/quality_output.rs +34 -0
- package/crates/naome-cli/src/quality_reconcile_command.rs +45 -0
- package/crates/naome-cli/src/repository_model_commands.rs +84 -0
- package/crates/naome-cli/src/task_commands.rs +62 -0
- package/crates/naome-cli/src/workflow_commands.rs +100 -3
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/context/helpers.rs +75 -0
- package/crates/naome-core/src/context/select.rs +134 -0
- package/crates/naome-core/src/context/types.rs +43 -0
- package/crates/naome-core/src/context.rs +6 -0
- package/crates/naome-core/src/decision/states.rs +1 -1
- package/crates/naome-core/src/decision.rs +4 -1
- package/crates/naome-core/src/install_plan.rs +18 -0
- package/crates/naome-core/src/intent/resolver_catalog/active.rs +38 -0
- package/crates/naome-core/src/intent/resolver_catalog/baseline.rs +44 -0
- package/crates/naome-core/src/intent/resolver_catalog/completed.rs +56 -0
- package/crates/naome-core/src/intent/resolver_catalog/dirty.rs +32 -0
- package/crates/naome-core/src/intent/resolver_catalog/ready.rs +32 -0
- package/crates/naome-core/src/intent/resolver_catalog/system.rs +20 -0
- package/crates/naome-core/src/intent/resolver_catalog.rs +12 -166
- package/crates/naome-core/src/journal.rs +2 -7
- package/crates/naome-core/src/lib.rs +33 -10
- package/crates/naome-core/src/quality/adapter_ios.rs +131 -0
- package/crates/naome-core/src/quality/adapter_support.rs +67 -0
- package/crates/naome-core/src/quality/adapters.rs +81 -18
- package/crates/naome-core/src/quality/cache.rs +7 -9
- package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +4 -7
- package/crates/naome-core/src/quality/config.rs +21 -3
- package/crates/naome-core/src/quality/mod.rs +138 -7
- package/crates/naome-core/src/quality/reconcile.rs +138 -0
- package/crates/naome-core/src/quality/reconcile_anchors.rs +64 -0
- package/crates/naome-core/src/quality/scanner/analysis.rs +20 -5
- package/crates/naome-core/src/quality/scanner.rs +62 -17
- package/crates/naome-core/src/quality/semantic/checks.rs +17 -0
- package/crates/naome-core/src/quality/semantic/route.rs +1 -1
- package/crates/naome-core/src/quality/structure/adapter_ios.rs +149 -0
- package/crates/naome-core/src/quality/structure/adapters.rs +60 -42
- package/crates/naome-core/src/quality/structure/checks/directory.rs +6 -4
- package/crates/naome-core/src/quality/structure/classify/roles.rs +51 -5
- package/crates/naome-core/src/quality/structure/config.rs +24 -3
- package/crates/naome-core/src/quality/structure/mod.rs +3 -0
- package/crates/naome-core/src/quality/types.rs +20 -1
- package/crates/naome-core/src/repository_model/detect.rs +188 -0
- package/crates/naome-core/src/repository_model/explain.rs +121 -0
- package/crates/naome-core/src/repository_model/path_scan.rs +67 -0
- package/crates/naome-core/src/repository_model/path_support.rs +59 -0
- package/crates/naome-core/src/repository_model/types.rs +152 -0
- package/crates/naome-core/src/repository_model/world.rs +48 -0
- package/crates/naome-core/src/repository_model/world_adapters.rs +145 -0
- package/crates/naome-core/src/repository_model/world_path_facts.rs +55 -0
- package/crates/naome-core/src/repository_model/world_paths.rs +168 -0
- package/crates/naome-core/src/repository_model.rs +164 -0
- package/crates/naome-core/src/route/builtin_checks.rs +40 -1
- package/crates/naome-core/src/task_ledger/import.rs +142 -0
- package/crates/naome-core/src/task_ledger/model.rs +13 -0
- package/crates/naome-core/src/task_ledger/proof_record.rs +52 -0
- package/crates/naome-core/src/task_ledger/read.rs +118 -0
- package/crates/naome-core/src/task_ledger/render.rs +55 -0
- package/crates/naome-core/src/task_ledger/write.rs +38 -0
- package/crates/naome-core/src/task_ledger.rs +48 -0
- package/crates/naome-core/src/task_state/api.rs +4 -2
- package/crates/naome-core/src/task_state/completed_refresh.rs +5 -16
- package/crates/naome-core/src/task_state/diff.rs +2 -2
- package/crates/naome-core/src/task_state/evidence.rs +8 -3
- package/crates/naome-core/src/task_state/mod.rs +1 -1
- package/crates/naome-core/src/task_state/progress.rs +13 -0
- package/crates/naome-core/src/task_state/proof_model.rs +8 -8
- package/crates/naome-core/src/task_state/repair.rs +2 -2
- package/crates/naome-core/src/task_state/task_diff_api.rs +9 -18
- package/crates/naome-core/src/task_state/types.rs +24 -0
- package/crates/naome-core/src/verification.rs +29 -18
- package/crates/naome-core/src/workflow/agent/capability.rs +194 -0
- package/crates/naome-core/src/workflow/agent/context_delta.rs +42 -0
- package/crates/naome-core/src/workflow/agent/decision.rs +32 -0
- package/crates/naome-core/src/workflow/agent/execution.rs +80 -0
- package/crates/naome-core/src/workflow/agent/proof.rs +24 -0
- package/crates/naome-core/src/workflow/agent/support.rs +58 -0
- package/crates/naome-core/src/workflow/agent/watchdog.rs +47 -0
- package/crates/naome-core/src/workflow/agent.rs +34 -0
- package/crates/naome-core/src/workflow/agent_types.rs +105 -0
- package/crates/naome-core/src/workflow/doctor.rs +39 -0
- package/crates/naome-core/src/workflow/mod.rs +11 -0
- package/crates/naome-core/src/workflow/output.rs +8 -2
- package/crates/naome-core/src/workflow/phase_inference.rs +1 -1
- package/crates/naome-core/tests/context.rs +99 -0
- package/crates/naome-core/tests/harness_health.rs +33 -40
- package/crates/naome-core/tests/install_plan.rs +12 -0
- package/crates/naome-core/tests/quality.rs +178 -2
- package/crates/naome-core/tests/quality_performance.rs +39 -2
- package/crates/naome-core/tests/quality_structure_adapters.rs +39 -0
- package/crates/naome-core/tests/repo_support/mod.rs +7 -1
- package/crates/naome-core/tests/repo_support/verification_values.rs +148 -1
- package/crates/naome-core/tests/repository_model.rs +281 -0
- package/crates/naome-core/tests/route_user_diff.rs +49 -1
- package/crates/naome-core/tests/semantic_legacy.rs +72 -38
- package/crates/naome-core/tests/task_ledger.rs +328 -0
- package/crates/naome-core/tests/task_state.rs +34 -14
- package/crates/naome-core/tests/task_state_support/mod.rs +2 -1
- package/crates/naome-core/tests/task_state_support/states.rs +28 -11
- package/crates/naome-core/tests/verification.rs +14 -39
- package/crates/naome-core/tests/verification_contract.rs +6 -52
- package/crates/naome-core/tests/workflow_agent.rs +233 -0
- package/crates/naome-core/tests/workflow_agent_support/mod.rs +159 -0
- package/crates/naome-core/tests/workflow_doctor.rs +21 -0
- package/crates/naome-core/tests/workflow_integrity.rs +2 -20
- package/crates/naome-core/tests/workflow_support/mod.rs +59 -20
- package/installer/codex-hooks.js +121 -0
- package/installer/context.js +10 -0
- package/installer/filesystem.js +4 -0
- package/installer/flows.js +8 -4
- package/installer/harness-files.js +6 -0
- package/installer/install-plan.js +4 -0
- package/installer/main.js +1 -1
- package/installer/native.js +1 -1
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +1 -1
- package/templates/naome-root/.codex/config.toml +2 -0
- package/templates/naome-root/.codex/hooks.json +70 -0
- package/templates/naome-root/.naome/bin/check-harness-health.js +8 -6
- package/templates/naome-root/.naome/bin/check-task-state.js +12 -7
- package/templates/naome-root/.naome/bin/codex-hook-io.js +122 -0
- package/templates/naome-root/.naome/bin/codex-hook-policy.js +180 -0
- package/templates/naome-root/.naome/bin/codex-hook-runtime.js +174 -0
- package/templates/naome-root/.naome/bin/codex-hook.js +6 -0
- package/templates/naome-root/.naome/bin/naome.js +35 -4
- package/templates/naome-root/.naome/manifest.json +12 -6
- package/templates/naome-root/.naome/repository-model.json +6 -0
- package/templates/naome-root/.naome/repository-quality.json +3 -1
- package/templates/naome-root/.naome/verification.json +15 -1
- package/templates/naome-root/AGENTS.md +38 -83
- package/templates/naome-root/docs/naome/agent-workflow.md +54 -18
- package/templates/naome-root/docs/naome/codex-hooks.md +82 -0
- package/templates/naome-root/docs/naome/context-economy.md +73 -0
- package/templates/naome-root/docs/naome/first-run.md +25 -14
- package/templates/naome-root/docs/naome/index.md +18 -10
- package/templates/naome-root/docs/naome/repository-model.md +92 -0
- package/templates/naome-root/docs/naome/repository-quality.md +47 -7
- package/templates/naome-root/docs/naome/repository-structure.md +10 -3
- package/templates/naome-root/docs/naome/task-ledger.md +71 -0
- package/templates/naome-root/docs/naome/testing.md +16 -3
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use naome_core::{explain_repository_model_path, refresh_repository_model, RepositoryModelRefresh};
|
|
4
|
+
|
|
5
|
+
use crate::cli_args::option_value;
|
|
6
|
+
|
|
7
|
+
pub fn run_repo_command(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
|
8
|
+
let Some(subcommand) = args.get(1).map(String::as_str) else {
|
|
9
|
+
return Err("naome repo requires model, check, or explain.".into());
|
|
10
|
+
};
|
|
11
|
+
let json = args.iter().any(|arg| arg == "--json");
|
|
12
|
+
|
|
13
|
+
match subcommand {
|
|
14
|
+
"model" => run_model(root, args, json)?,
|
|
15
|
+
"check" => run_check(root, json)?,
|
|
16
|
+
"explain" => run_explain(root, args, json)?,
|
|
17
|
+
_ => return Err(format!("unknown naome repo command: {subcommand}").into()),
|
|
18
|
+
}
|
|
19
|
+
Ok(())
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fn run_model(root: &Path, args: &[String], json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|
23
|
+
let report = refresh_repository_model(root, args.iter().any(|arg| arg == "--write"))?;
|
|
24
|
+
print_refresh_report(&report, json)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
fn run_check(root: &Path, json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|
28
|
+
let report = refresh_repository_model(root, false)?;
|
|
29
|
+
print_refresh_report(&report, json)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn print_refresh_report(
|
|
33
|
+
report: &RepositoryModelRefresh,
|
|
34
|
+
json: bool,
|
|
35
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
36
|
+
if json {
|
|
37
|
+
println!("{}", serde_json::to_string_pretty(&report)?);
|
|
38
|
+
} else if !report.updated_paths.is_empty() {
|
|
39
|
+
println!(
|
|
40
|
+
"NAOME repository model updated: {}.",
|
|
41
|
+
report.updated_paths.join(", ")
|
|
42
|
+
);
|
|
43
|
+
} else if report.ok {
|
|
44
|
+
println!("NAOME repository model is current.");
|
|
45
|
+
} else {
|
|
46
|
+
println!("NAOME repository model is stale.");
|
|
47
|
+
println!("Run naome repo model --write to refresh deterministic repository facts.");
|
|
48
|
+
}
|
|
49
|
+
if !report.ok {
|
|
50
|
+
std::process::exit(1);
|
|
51
|
+
}
|
|
52
|
+
Ok(())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
fn run_explain(root: &Path, args: &[String], json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|
56
|
+
let Some(path) = option_value(args, "--path") else {
|
|
57
|
+
return Err("naome repo explain requires --path <path>.".into());
|
|
58
|
+
};
|
|
59
|
+
let explanation = explain_repository_model_path(root, path)?;
|
|
60
|
+
if json {
|
|
61
|
+
println!("{}", serde_json::to_string_pretty(&explanation)?);
|
|
62
|
+
} else {
|
|
63
|
+
println!(
|
|
64
|
+
"{} role={} language={} module={} entity={}",
|
|
65
|
+
explanation.path,
|
|
66
|
+
explanation.role.as_deref().unwrap_or("unknown"),
|
|
67
|
+
explanation.language.as_deref().unwrap_or("unknown"),
|
|
68
|
+
explanation.module.as_deref().unwrap_or("unknown"),
|
|
69
|
+
explanation.entity.as_deref().unwrap_or("unknown")
|
|
70
|
+
);
|
|
71
|
+
if !explanation.facts.is_empty() {
|
|
72
|
+
println!(
|
|
73
|
+
"Facts: {}",
|
|
74
|
+
explanation
|
|
75
|
+
.facts
|
|
76
|
+
.iter()
|
|
77
|
+
.map(|fact| fact.id.as_str())
|
|
78
|
+
.collect::<Vec<_>>()
|
|
79
|
+
.join(", ")
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
Ok(())
|
|
84
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use naome_core::{migrate_task_state_to_ledger, render_task_state_from_ledger};
|
|
4
|
+
|
|
5
|
+
pub fn run_task_command(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
|
6
|
+
match args.get(1).map(String::as_str) {
|
|
7
|
+
Some("render-state") => render_state(root, args),
|
|
8
|
+
Some("migrate-ledger") => migrate_ledger(root, args),
|
|
9
|
+
_ => Err("unknown task command".into()),
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fn render_state(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
|
14
|
+
let write = args.iter().any(|arg| arg == "--write");
|
|
15
|
+
let json = args.iter().any(|arg| arg == "--json");
|
|
16
|
+
let rendered = render_task_state_from_ledger(root, write)?;
|
|
17
|
+
|
|
18
|
+
if json {
|
|
19
|
+
println!(
|
|
20
|
+
"{}",
|
|
21
|
+
serde_json::to_string_pretty(&serde_json::json!({
|
|
22
|
+
"schema": "naome.task-render-state.v1",
|
|
23
|
+
"updated": write && rendered.is_some(),
|
|
24
|
+
"source": if rendered.is_some() { "ledger" } else { "task-state" },
|
|
25
|
+
"taskState": rendered
|
|
26
|
+
}))?
|
|
27
|
+
);
|
|
28
|
+
} else if rendered.is_some() {
|
|
29
|
+
println!("NAOME task-state projection rendered from task ledger.");
|
|
30
|
+
} else {
|
|
31
|
+
println!(
|
|
32
|
+
"NAOME task ledger not found; existing task-state compatibility mode remains active."
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
Ok(())
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn migrate_ledger(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
|
40
|
+
let write = args.iter().any(|arg| arg == "--write");
|
|
41
|
+
let json = args.iter().any(|arg| arg == "--json");
|
|
42
|
+
let migrated = migrate_task_state_to_ledger(root, write)?;
|
|
43
|
+
|
|
44
|
+
if json {
|
|
45
|
+
println!(
|
|
46
|
+
"{}",
|
|
47
|
+
serde_json::to_string_pretty(&serde_json::json!({
|
|
48
|
+
"schema": "naome.task-migrate-ledger.v1",
|
|
49
|
+
"updated": write && migrated.is_some(),
|
|
50
|
+
"migration": migrated
|
|
51
|
+
}))?
|
|
52
|
+
);
|
|
53
|
+
} else if migrated.is_some() && write {
|
|
54
|
+
println!("NAOME task ledger migrated from task-state.");
|
|
55
|
+
} else if migrated.is_some() {
|
|
56
|
+
println!("NAOME task ledger migration is available; rerun with --write to apply it.");
|
|
57
|
+
} else {
|
|
58
|
+
println!("NAOME task ledger migration skipped; no active task-state task exists.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Ok(())
|
|
62
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
use std::path::Path;
|
|
2
2
|
|
|
3
3
|
use naome_core::{
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
agent_run_plan, classify_mutations, context_delta_report, decision_gate, doctor_report,
|
|
5
|
+
edit_session_watchdog, proof_plan_for_task, refresh_integrity, repository_capability_graph,
|
|
6
|
+
safe_rg_args, summarize_command_output, tracked_process_report, validate_search_command,
|
|
7
|
+
verification_phase_plan,
|
|
6
8
|
};
|
|
7
9
|
|
|
8
10
|
use crate::cli_args::option_value;
|
|
@@ -30,7 +32,7 @@ pub fn run_workflow_command(
|
|
|
30
32
|
args: &[String],
|
|
31
33
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
32
34
|
let Some(subcommand) = args.get(1).map(String::as_str) else {
|
|
33
|
-
return Err("naome workflow requires search-profile, check-search, phases, processes, or mutations.".into());
|
|
35
|
+
return Err("naome workflow requires agent-plan, context-delta, proof-plan, capabilities, edit-watchdog, decision-gate, digest, search-profile, check-search, phases, processes, or mutations.".into());
|
|
34
36
|
};
|
|
35
37
|
let json = args.iter().any(|arg| arg == "--json");
|
|
36
38
|
|
|
@@ -50,6 +52,29 @@ pub fn run_workflow_command(
|
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
"check-search" => run_check_search(root, args, json)?,
|
|
55
|
+
"agent-plan" => print_json_or_label(
|
|
56
|
+
serde_json::to_value(agent_run_plan(root)?)?,
|
|
57
|
+
json,
|
|
58
|
+
"NAOME agent plan ready.",
|
|
59
|
+
)?,
|
|
60
|
+
"context-delta" => run_context_delta(root, args, json)?,
|
|
61
|
+
"proof-plan" => print_json_or_label(
|
|
62
|
+
serde_json::to_value(proof_plan_for_task(root, &[])?)?,
|
|
63
|
+
json,
|
|
64
|
+
"NAOME proof plan ready.",
|
|
65
|
+
)?,
|
|
66
|
+
"capabilities" => print_json_or_label(
|
|
67
|
+
serde_json::to_value(repository_capability_graph(root)?)?,
|
|
68
|
+
json,
|
|
69
|
+
"NAOME capability graph ready.",
|
|
70
|
+
)?,
|
|
71
|
+
"edit-watchdog" => run_edit_watchdog(root, args, json)?,
|
|
72
|
+
"decision-gate" => print_json_or_label(
|
|
73
|
+
serde_json::to_value(decision_gate(root)?)?,
|
|
74
|
+
json,
|
|
75
|
+
"NAOME decision gate ready.",
|
|
76
|
+
)?,
|
|
77
|
+
"digest" => run_digest(root, args, json)?,
|
|
53
78
|
"phases" => {
|
|
54
79
|
let plan = verification_phase_plan(root, &[])?;
|
|
55
80
|
if json {
|
|
@@ -154,6 +179,78 @@ fn run_mutations(
|
|
|
154
179
|
Ok(())
|
|
155
180
|
}
|
|
156
181
|
|
|
182
|
+
fn run_context_delta(
|
|
183
|
+
root: &Path,
|
|
184
|
+
args: &[String],
|
|
185
|
+
json: bool,
|
|
186
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
187
|
+
let read_paths = repeated_values(args, "--read-path");
|
|
188
|
+
print_json_or_label(
|
|
189
|
+
serde_json::to_value(context_delta_report(root, &read_paths)?)?,
|
|
190
|
+
json,
|
|
191
|
+
"NAOME context delta ready.",
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
fn run_edit_watchdog(
|
|
196
|
+
root: &Path,
|
|
197
|
+
args: &[String],
|
|
198
|
+
json: bool,
|
|
199
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
200
|
+
let paths = repeated_values(args, "--path");
|
|
201
|
+
if paths.is_empty() {
|
|
202
|
+
return Err("naome workflow edit-watchdog requires --path <path>.".into());
|
|
203
|
+
}
|
|
204
|
+
print_json_or_label(
|
|
205
|
+
serde_json::to_value(edit_session_watchdog(root, &paths)?)?,
|
|
206
|
+
json,
|
|
207
|
+
"NAOME edit watchdog ready.",
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
fn run_digest(root: &Path, args: &[String], json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|
212
|
+
let command = option_value(args, "--command").unwrap_or_else(|| "unknown".to_string());
|
|
213
|
+
let cwd = option_value(args, "--cwd").unwrap_or_else(|| ".".to_string());
|
|
214
|
+
let exit_code = option_value(args, "--exit-code")
|
|
215
|
+
.and_then(|value| value.parse::<i32>().ok())
|
|
216
|
+
.unwrap_or(1);
|
|
217
|
+
let max_lines = option_value(args, "--max-lines")
|
|
218
|
+
.and_then(|value| value.parse::<usize>().ok())
|
|
219
|
+
.unwrap_or(12);
|
|
220
|
+
let output = if let Some(path) = option_value(args, "--output-file") {
|
|
221
|
+
std::fs::read_to_string(root.join(path))?
|
|
222
|
+
} else {
|
|
223
|
+
option_value(args, "--output").unwrap_or_default()
|
|
224
|
+
};
|
|
225
|
+
print_json_or_label(
|
|
226
|
+
serde_json::to_value(summarize_command_output(
|
|
227
|
+
&command, &cwd, exit_code, &output, max_lines,
|
|
228
|
+
))?,
|
|
229
|
+
json,
|
|
230
|
+
"NAOME output digest ready.",
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
fn repeated_values(args: &[String], flag: &str) -> Vec<String> {
|
|
235
|
+
args.windows(2)
|
|
236
|
+
.filter(|window| window[0] == flag)
|
|
237
|
+
.map(|window| window[1].clone())
|
|
238
|
+
.collect()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
fn print_json_or_label(
|
|
242
|
+
value: serde_json::Value,
|
|
243
|
+
json: bool,
|
|
244
|
+
label: &str,
|
|
245
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
246
|
+
if json {
|
|
247
|
+
println!("{}", serde_json::to_string_pretty(&value)?);
|
|
248
|
+
} else {
|
|
249
|
+
println!("{label}");
|
|
250
|
+
}
|
|
251
|
+
Ok(())
|
|
252
|
+
}
|
|
253
|
+
|
|
157
254
|
fn empty_label(values: &[String]) -> String {
|
|
158
255
|
if values.is_empty() {
|
|
159
256
|
"none".to_string()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
use std::collections::BTreeSet;
|
|
2
|
+
use std::fs;
|
|
3
|
+
use std::path::Path;
|
|
4
|
+
|
|
5
|
+
use super::types::{ContextCapsule, ContextItem};
|
|
6
|
+
|
|
7
|
+
pub(super) fn normalize_paths(paths: Vec<String>) -> Vec<String> {
|
|
8
|
+
let normalized = paths
|
|
9
|
+
.into_iter()
|
|
10
|
+
.filter(|path| !path.trim().is_empty())
|
|
11
|
+
.map(|path| path.trim_start_matches("./").replace('\\', "/"))
|
|
12
|
+
.collect::<BTreeSet<_>>()
|
|
13
|
+
.into_iter()
|
|
14
|
+
.collect::<Vec<_>>();
|
|
15
|
+
let task_paths = normalized
|
|
16
|
+
.iter()
|
|
17
|
+
.filter(|path| !is_context_noise(path))
|
|
18
|
+
.cloned()
|
|
19
|
+
.collect::<Vec<_>>();
|
|
20
|
+
if task_paths.is_empty() {
|
|
21
|
+
normalized
|
|
22
|
+
} else {
|
|
23
|
+
task_paths
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub(super) fn context_item(root: &Path, path: &str, reason: &str, priority: &str) -> ContextItem {
|
|
28
|
+
ContextItem {
|
|
29
|
+
path: path.to_string(),
|
|
30
|
+
reason: reason.to_string(),
|
|
31
|
+
priority: priority.to_string(),
|
|
32
|
+
estimated_lines: fs::read_to_string(root.join(path))
|
|
33
|
+
.map(|content| content.lines().count().max(1))
|
|
34
|
+
.unwrap_or(1),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub(super) fn capsule_for_paths(paths: &[String]) -> ContextCapsule {
|
|
39
|
+
let id = if paths.iter().any(|path| path.contains("quality")) {
|
|
40
|
+
"quality-work"
|
|
41
|
+
} else if paths
|
|
42
|
+
.iter()
|
|
43
|
+
.any(|path| path.contains("/route") || path.contains("/intent"))
|
|
44
|
+
{
|
|
45
|
+
"routing-work"
|
|
46
|
+
} else if paths.iter().any(|path| is_source_path(path)) {
|
|
47
|
+
"source-work"
|
|
48
|
+
} else if paths
|
|
49
|
+
.iter()
|
|
50
|
+
.any(|path| path.ends_with(".md") || path.contains("/docs/"))
|
|
51
|
+
{
|
|
52
|
+
"docs-work"
|
|
53
|
+
} else {
|
|
54
|
+
"general-work"
|
|
55
|
+
};
|
|
56
|
+
ContextCapsule {
|
|
57
|
+
id: id.to_string(),
|
|
58
|
+
task_type: id.trim_end_matches("-work").to_string(),
|
|
59
|
+
summary: format!("Compact context capsule for {id}."),
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
pub(super) fn is_source_path(path: &str) -> bool {
|
|
64
|
+
let lower = path.to_ascii_lowercase();
|
|
65
|
+
[
|
|
66
|
+
".rs", ".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".py", ".go", ".swift", ".kt",
|
|
67
|
+
".java", ".c", ".cc", ".cpp", ".h", ".hpp", ".rb", ".php", ".cs", ".sh",
|
|
68
|
+
]
|
|
69
|
+
.iter()
|
|
70
|
+
.any(|extension| lower.ends_with(extension))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn is_context_noise(path: &str) -> bool {
|
|
74
|
+
matches!(path, ".naome/task-state.json" | ".naome/manifest.json") || path.contains("/native/")
|
|
75
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
use std::path::Path;
|
|
2
|
+
|
|
3
|
+
use crate::{git, models::NaomeError};
|
|
4
|
+
|
|
5
|
+
use super::helpers::{capsule_for_paths, context_item, is_source_path, normalize_paths};
|
|
6
|
+
use super::types::{ContextBudgetLedger, ContextItem, ContextSelection};
|
|
7
|
+
|
|
8
|
+
const MAX_CONTEXT_FILES: usize = 12;
|
|
9
|
+
|
|
10
|
+
pub fn select_context_for_changed_paths(root: &Path) -> Result<ContextSelection, NaomeError> {
|
|
11
|
+
let paths = git::changed_paths(root)?;
|
|
12
|
+
Ok(selection_for_paths(root, "changed", paths))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub fn select_context_for_prompt(
|
|
16
|
+
root: &Path,
|
|
17
|
+
prompt: impl AsRef<str>,
|
|
18
|
+
) -> Result<ContextSelection, NaomeError> {
|
|
19
|
+
Ok(selection_for_paths(
|
|
20
|
+
root,
|
|
21
|
+
"prompt",
|
|
22
|
+
mentioned_paths(prompt.as_ref()),
|
|
23
|
+
))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fn selection_for_paths(root: &Path, mode: &str, paths: Vec<String>) -> ContextSelection {
|
|
27
|
+
let target_paths = normalize_paths(paths);
|
|
28
|
+
let capsule = capsule_for_paths(&target_paths);
|
|
29
|
+
let mut required_context = target_paths
|
|
30
|
+
.iter()
|
|
31
|
+
.take(MAX_CONTEXT_FILES)
|
|
32
|
+
.map(|path| context_item(root, path, "changed_path", "required"))
|
|
33
|
+
.collect::<Vec<_>>();
|
|
34
|
+
if required_context.is_empty() {
|
|
35
|
+
required_context.push(context_item(
|
|
36
|
+
root,
|
|
37
|
+
".naome/verification.json",
|
|
38
|
+
"minimal_harness_policy",
|
|
39
|
+
"required",
|
|
40
|
+
));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let avoided_files = target_paths.len().saturating_sub(required_context.len());
|
|
44
|
+
ContextSelection {
|
|
45
|
+
schema: "naome.context-selection.v1".to_string(),
|
|
46
|
+
mode: mode.to_string(),
|
|
47
|
+
optional_context: optional_context(root, &capsule.id),
|
|
48
|
+
forbidden_context: forbidden_context(),
|
|
49
|
+
commands: commands_for_paths(&target_paths),
|
|
50
|
+
budget_ledger: budget_ledger(&required_context, avoided_files),
|
|
51
|
+
notes: vec![
|
|
52
|
+
"Read requiredContext first; read optionalContext only when the task is blocked without it.".to_string(),
|
|
53
|
+
"Use path-scoped checks during implementation and keep the final changed gate before completion.".to_string(),
|
|
54
|
+
],
|
|
55
|
+
capsule,
|
|
56
|
+
required_context,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn optional_context(root: &Path, capsule_id: &str) -> Vec<ContextItem> {
|
|
61
|
+
let candidates = match capsule_id {
|
|
62
|
+
"quality-work" => vec!["docs/naome/repository-quality.md", "docs/naome/testing.md"],
|
|
63
|
+
"routing-work" => vec!["docs/naome/agent-workflow.md", "docs/naome/testing.md"],
|
|
64
|
+
"docs-work" => vec!["docs/naome/index.md", "docs/naome/testing.md"],
|
|
65
|
+
_ => vec!["docs/naome/repository-quality.md", "docs/naome/testing.md"],
|
|
66
|
+
};
|
|
67
|
+
candidates
|
|
68
|
+
.into_iter()
|
|
69
|
+
.filter(|path| root.join(path).is_file())
|
|
70
|
+
.map(|path| context_item(root, path, "optional_capsule_context", "optional"))
|
|
71
|
+
.collect()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fn budget_ledger(required_context: &[ContextItem], avoided_files: usize) -> ContextBudgetLedger {
|
|
75
|
+
let estimated_lines = required_context
|
|
76
|
+
.iter()
|
|
77
|
+
.map(|item| item.estimated_lines)
|
|
78
|
+
.sum::<usize>();
|
|
79
|
+
ContextBudgetLedger {
|
|
80
|
+
max_context_files: MAX_CONTEXT_FILES,
|
|
81
|
+
selected_files: required_context.len(),
|
|
82
|
+
avoided_files,
|
|
83
|
+
estimated_lines,
|
|
84
|
+
estimated_tokens: estimated_lines.saturating_mul(8),
|
|
85
|
+
reason_codes: if avoided_files > 0 {
|
|
86
|
+
vec!["context_file_budget_reached".to_string()]
|
|
87
|
+
} else {
|
|
88
|
+
Vec::new()
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn commands_for_paths(paths: &[String]) -> Vec<String> {
|
|
94
|
+
let mut commands = paths
|
|
95
|
+
.iter()
|
|
96
|
+
.filter(|path| is_source_path(path))
|
|
97
|
+
.take(6)
|
|
98
|
+
.map(|path| format!("node .naome/bin/naome.js quality check --path {path}"))
|
|
99
|
+
.collect::<Vec<_>>();
|
|
100
|
+
commands.push("node .naome/bin/naome.js quality check --changed".to_string());
|
|
101
|
+
commands.push("git diff --check".to_string());
|
|
102
|
+
commands.sort();
|
|
103
|
+
commands.dedup();
|
|
104
|
+
commands
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fn forbidden_context() -> Vec<String> {
|
|
108
|
+
[
|
|
109
|
+
".git/**",
|
|
110
|
+
".naome/archive/**",
|
|
111
|
+
".naome/cache/**",
|
|
112
|
+
"node_modules/**",
|
|
113
|
+
"target/**",
|
|
114
|
+
"dist/**",
|
|
115
|
+
"build/**",
|
|
116
|
+
]
|
|
117
|
+
.into_iter()
|
|
118
|
+
.map(str::to_string)
|
|
119
|
+
.collect()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn mentioned_paths(prompt: &str) -> Vec<String> {
|
|
123
|
+
prompt
|
|
124
|
+
.split_whitespace()
|
|
125
|
+
.map(|token| {
|
|
126
|
+
token.trim_matches(|ch: char| {
|
|
127
|
+
matches!(ch, '"' | '\'' | '`' | ',' | ';' | ':' | ')' | '(')
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
.filter(|token| token.contains('/') || is_source_path(token) || token.ends_with(".md"))
|
|
131
|
+
.filter(|token| !token.starts_with('-') && !token.starts_with("http"))
|
|
132
|
+
.map(|token| token.trim_start_matches("./").replace('\\', "/"))
|
|
133
|
+
.collect()
|
|
134
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
use serde::Serialize;
|
|
2
|
+
|
|
3
|
+
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
4
|
+
#[serde(rename_all = "camelCase")]
|
|
5
|
+
pub struct ContextSelection {
|
|
6
|
+
pub schema: String,
|
|
7
|
+
pub mode: String,
|
|
8
|
+
pub capsule: ContextCapsule,
|
|
9
|
+
pub required_context: Vec<ContextItem>,
|
|
10
|
+
pub optional_context: Vec<ContextItem>,
|
|
11
|
+
pub forbidden_context: Vec<String>,
|
|
12
|
+
pub commands: Vec<String>,
|
|
13
|
+
pub budget_ledger: ContextBudgetLedger,
|
|
14
|
+
pub notes: Vec<String>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
18
|
+
#[serde(rename_all = "camelCase")]
|
|
19
|
+
pub struct ContextCapsule {
|
|
20
|
+
pub id: String,
|
|
21
|
+
pub summary: String,
|
|
22
|
+
pub task_type: String,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
26
|
+
#[serde(rename_all = "camelCase")]
|
|
27
|
+
pub struct ContextItem {
|
|
28
|
+
pub path: String,
|
|
29
|
+
pub reason: String,
|
|
30
|
+
pub priority: String,
|
|
31
|
+
pub estimated_lines: usize,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
35
|
+
#[serde(rename_all = "camelCase")]
|
|
36
|
+
pub struct ContextBudgetLedger {
|
|
37
|
+
pub max_context_files: usize,
|
|
38
|
+
pub selected_files: usize,
|
|
39
|
+
pub avoided_files: usize,
|
|
40
|
+
pub estimated_lines: usize,
|
|
41
|
+
pub estimated_tokens: usize,
|
|
42
|
+
pub reason_codes: Vec<String>,
|
|
43
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
use std::path::Path;
|
|
2
2
|
|
|
3
|
+
use serde_json::Value;
|
|
4
|
+
|
|
3
5
|
use crate::git;
|
|
4
6
|
use crate::models::{CheckDecision, Decision, NaomeError, TaskDecision};
|
|
7
|
+
use crate::task_ledger::read_task_state_projection;
|
|
5
8
|
|
|
6
9
|
mod checks;
|
|
7
10
|
mod idle;
|
|
@@ -40,7 +43,7 @@ pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Deci
|
|
|
40
43
|
return Ok(decision);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
let task_state =
|
|
46
|
+
let task_state = read_task_state_projection(root)?.unwrap_or(Value::Null);
|
|
44
47
|
if let Some(decision) = setup_block_decision(root, &changed_paths, &harness_health)? {
|
|
45
48
|
return Ok(decision);
|
|
46
49
|
}
|
|
@@ -10,6 +10,8 @@ pub const MACHINE_OWNED_PATHS: &[&str] = &[
|
|
|
10
10
|
"docs/naome/index.md",
|
|
11
11
|
"docs/naome/first-run.md",
|
|
12
12
|
"docs/naome/agent-workflow.md",
|
|
13
|
+
"docs/naome/context-economy.md",
|
|
14
|
+
"docs/naome/task-ledger.md",
|
|
13
15
|
"docs/naome/execution.md",
|
|
14
16
|
"docs/naome/upgrade.md",
|
|
15
17
|
];
|
|
@@ -21,12 +23,14 @@ pub const PROJECT_OWNED_PATHS: &[&str] = &[
|
|
|
21
23
|
".naome/task-state.json",
|
|
22
24
|
".naome/upgrade-state.json",
|
|
23
25
|
".naome/verification.json",
|
|
26
|
+
".naome/repository-model.json",
|
|
24
27
|
".naome/repository-quality.json",
|
|
25
28
|
".naome/repository-structure.json",
|
|
26
29
|
".naome/repository-quality-baseline.json",
|
|
27
30
|
"docs/naome/architecture.md",
|
|
28
31
|
"docs/naome/decisions.md",
|
|
29
32
|
"docs/naome/repo-profile.md",
|
|
33
|
+
"docs/naome/repository-model.md",
|
|
30
34
|
"docs/naome/repository-quality.md",
|
|
31
35
|
"docs/naome/repository-structure.md",
|
|
32
36
|
"docs/naome/security.md",
|
|
@@ -42,6 +46,8 @@ pub const LOCAL_ONLY_MACHINE_OWNED_PATHS: &[&str] = &[
|
|
|
42
46
|
"docs/naome/index.md",
|
|
43
47
|
"docs/naome/first-run.md",
|
|
44
48
|
"docs/naome/agent-workflow.md",
|
|
49
|
+
"docs/naome/context-economy.md",
|
|
50
|
+
"docs/naome/task-ledger.md",
|
|
45
51
|
"docs/naome/execution.md",
|
|
46
52
|
"docs/naome/upgrade.md",
|
|
47
53
|
];
|
|
@@ -54,6 +60,16 @@ pub const LOCAL_NATIVE_BINARY_PATHS: &[&str] = &[
|
|
|
54
60
|
".naome/task-journal.jsonl",
|
|
55
61
|
];
|
|
56
62
|
|
|
63
|
+
pub const OPTIONAL_CODEX_HOOK_PATHS: &[&str] = &[
|
|
64
|
+
".codex/config.toml",
|
|
65
|
+
".codex/hooks.json",
|
|
66
|
+
".naome/bin/codex-hook.js",
|
|
67
|
+
".naome/bin/codex-hook-io.js",
|
|
68
|
+
".naome/bin/codex-hook-policy.js",
|
|
69
|
+
".naome/bin/codex-hook-runtime.js",
|
|
70
|
+
"docs/naome/codex-hooks.md",
|
|
71
|
+
];
|
|
72
|
+
|
|
57
73
|
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
58
74
|
#[serde(rename_all = "camelCase")]
|
|
59
75
|
pub struct InstallPlan {
|
|
@@ -62,6 +78,7 @@ pub struct InstallPlan {
|
|
|
62
78
|
pub machine_owned: Vec<&'static str>,
|
|
63
79
|
pub project_owned: Vec<&'static str>,
|
|
64
80
|
pub local_only_machine_owned: Vec<&'static str>,
|
|
81
|
+
pub optional_codex_hook_paths: Vec<&'static str>,
|
|
65
82
|
pub gitignore_entries: Vec<&'static str>,
|
|
66
83
|
pub git_exclude_entries: Vec<&'static str>,
|
|
67
84
|
pub git_untrack_paths: Vec<&'static str>,
|
|
@@ -87,6 +104,7 @@ pub fn install_plan(harness_version: impl Into<String>) -> InstallPlan {
|
|
|
87
104
|
machine_owned: MACHINE_OWNED_PATHS.to_vec(),
|
|
88
105
|
project_owned: PROJECT_OWNED_PATHS.to_vec(),
|
|
89
106
|
local_only_machine_owned: LOCAL_ONLY_MACHINE_OWNED_PATHS.to_vec(),
|
|
107
|
+
optional_codex_hook_paths: OPTIONAL_CODEX_HOOK_PATHS.to_vec(),
|
|
90
108
|
gitignore_entries,
|
|
91
109
|
git_exclude_entries,
|
|
92
110
|
git_untrack_paths,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
use super::super::resolver_shared::Policy;
|
|
2
|
+
|
|
3
|
+
pub(crate) const POLICY_ACTIVE_BLOCKED: Policy = (
|
|
4
|
+
"block_unowned_diff",
|
|
5
|
+
false,
|
|
6
|
+
"The active task is blocked by out-of-scope changes.",
|
|
7
|
+
"Resolve the scope blocker before interpreting new work.",
|
|
8
|
+
);
|
|
9
|
+
pub(crate) const POLICY_ACTIVE_CANCEL: Policy = (
|
|
10
|
+
"cancel_current_task",
|
|
11
|
+
false,
|
|
12
|
+
"The prompt asks to cancel the active task.",
|
|
13
|
+
"Cancel or review the active task before starting another.",
|
|
14
|
+
);
|
|
15
|
+
pub(crate) const POLICY_ACTIVE_CONTINUE: Policy = (
|
|
16
|
+
"continue_current_task",
|
|
17
|
+
true,
|
|
18
|
+
"A task is active and the prompt appears to continue or finish it.",
|
|
19
|
+
"Continue the current task and keep proof current.",
|
|
20
|
+
);
|
|
21
|
+
pub(crate) const POLICY_ACTIVE_NEW_BLOCK: Policy = (
|
|
22
|
+
"block_ambiguous_intent",
|
|
23
|
+
false,
|
|
24
|
+
"A task is already active and the prompt appears to ask for a distinct new goal.",
|
|
25
|
+
"Complete, revise, or cancel the active task before starting another.",
|
|
26
|
+
);
|
|
27
|
+
pub(crate) const POLICY_ACTIVE_NO_COMMIT: Policy = (
|
|
28
|
+
"continue_current_task_without_commit",
|
|
29
|
+
true,
|
|
30
|
+
"A task is active and the prompt blocks committing.",
|
|
31
|
+
"Continue the active task without baselining.",
|
|
32
|
+
);
|
|
33
|
+
pub(crate) const POLICY_ACTIVE_REVIEW: Policy = (
|
|
34
|
+
"review_current_task_diff",
|
|
35
|
+
false,
|
|
36
|
+
"The prompt asks to review the active task diff.",
|
|
37
|
+
"Review the active task before mutating it further.",
|
|
38
|
+
);
|