@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.
Files changed (149) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +11 -2
  3. package/bin/naome.js +62 -24
  4. package/crates/naome-cli/Cargo.toml +1 -1
  5. package/crates/naome-cli/src/context_commands.rs +47 -0
  6. package/crates/naome-cli/src/dispatcher.rs +6 -0
  7. package/crates/naome-cli/src/main.rs +43 -6
  8. package/crates/naome-cli/src/quality_commands.rs +31 -46
  9. package/crates/naome-cli/src/quality_output.rs +34 -0
  10. package/crates/naome-cli/src/quality_reconcile_command.rs +45 -0
  11. package/crates/naome-cli/src/repository_model_commands.rs +84 -0
  12. package/crates/naome-cli/src/task_commands.rs +62 -0
  13. package/crates/naome-cli/src/workflow_commands.rs +100 -3
  14. package/crates/naome-core/Cargo.toml +1 -1
  15. package/crates/naome-core/src/context/helpers.rs +75 -0
  16. package/crates/naome-core/src/context/select.rs +134 -0
  17. package/crates/naome-core/src/context/types.rs +43 -0
  18. package/crates/naome-core/src/context.rs +6 -0
  19. package/crates/naome-core/src/decision/states.rs +1 -1
  20. package/crates/naome-core/src/decision.rs +4 -1
  21. package/crates/naome-core/src/install_plan.rs +18 -0
  22. package/crates/naome-core/src/intent/resolver_catalog/active.rs +38 -0
  23. package/crates/naome-core/src/intent/resolver_catalog/baseline.rs +44 -0
  24. package/crates/naome-core/src/intent/resolver_catalog/completed.rs +56 -0
  25. package/crates/naome-core/src/intent/resolver_catalog/dirty.rs +32 -0
  26. package/crates/naome-core/src/intent/resolver_catalog/ready.rs +32 -0
  27. package/crates/naome-core/src/intent/resolver_catalog/system.rs +20 -0
  28. package/crates/naome-core/src/intent/resolver_catalog.rs +12 -166
  29. package/crates/naome-core/src/journal.rs +2 -7
  30. package/crates/naome-core/src/lib.rs +33 -10
  31. package/crates/naome-core/src/quality/adapter_ios.rs +131 -0
  32. package/crates/naome-core/src/quality/adapter_support.rs +67 -0
  33. package/crates/naome-core/src/quality/adapters.rs +81 -18
  34. package/crates/naome-core/src/quality/cache.rs +7 -9
  35. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +4 -7
  36. package/crates/naome-core/src/quality/config.rs +21 -3
  37. package/crates/naome-core/src/quality/mod.rs +138 -7
  38. package/crates/naome-core/src/quality/reconcile.rs +138 -0
  39. package/crates/naome-core/src/quality/reconcile_anchors.rs +64 -0
  40. package/crates/naome-core/src/quality/scanner/analysis.rs +20 -5
  41. package/crates/naome-core/src/quality/scanner.rs +62 -17
  42. package/crates/naome-core/src/quality/semantic/checks.rs +17 -0
  43. package/crates/naome-core/src/quality/semantic/route.rs +1 -1
  44. package/crates/naome-core/src/quality/structure/adapter_ios.rs +149 -0
  45. package/crates/naome-core/src/quality/structure/adapters.rs +60 -42
  46. package/crates/naome-core/src/quality/structure/checks/directory.rs +6 -4
  47. package/crates/naome-core/src/quality/structure/classify/roles.rs +51 -5
  48. package/crates/naome-core/src/quality/structure/config.rs +24 -3
  49. package/crates/naome-core/src/quality/structure/mod.rs +3 -0
  50. package/crates/naome-core/src/quality/types.rs +20 -1
  51. package/crates/naome-core/src/repository_model/detect.rs +188 -0
  52. package/crates/naome-core/src/repository_model/explain.rs +121 -0
  53. package/crates/naome-core/src/repository_model/path_scan.rs +67 -0
  54. package/crates/naome-core/src/repository_model/path_support.rs +59 -0
  55. package/crates/naome-core/src/repository_model/types.rs +152 -0
  56. package/crates/naome-core/src/repository_model/world.rs +48 -0
  57. package/crates/naome-core/src/repository_model/world_adapters.rs +145 -0
  58. package/crates/naome-core/src/repository_model/world_path_facts.rs +55 -0
  59. package/crates/naome-core/src/repository_model/world_paths.rs +168 -0
  60. package/crates/naome-core/src/repository_model.rs +164 -0
  61. package/crates/naome-core/src/route/builtin_checks.rs +40 -1
  62. package/crates/naome-core/src/task_ledger/import.rs +142 -0
  63. package/crates/naome-core/src/task_ledger/model.rs +13 -0
  64. package/crates/naome-core/src/task_ledger/proof_record.rs +52 -0
  65. package/crates/naome-core/src/task_ledger/read.rs +118 -0
  66. package/crates/naome-core/src/task_ledger/render.rs +55 -0
  67. package/crates/naome-core/src/task_ledger/write.rs +38 -0
  68. package/crates/naome-core/src/task_ledger.rs +48 -0
  69. package/crates/naome-core/src/task_state/api.rs +4 -2
  70. package/crates/naome-core/src/task_state/completed_refresh.rs +5 -16
  71. package/crates/naome-core/src/task_state/diff.rs +2 -2
  72. package/crates/naome-core/src/task_state/evidence.rs +8 -3
  73. package/crates/naome-core/src/task_state/mod.rs +1 -1
  74. package/crates/naome-core/src/task_state/progress.rs +13 -0
  75. package/crates/naome-core/src/task_state/proof_model.rs +8 -8
  76. package/crates/naome-core/src/task_state/repair.rs +2 -2
  77. package/crates/naome-core/src/task_state/task_diff_api.rs +9 -18
  78. package/crates/naome-core/src/task_state/types.rs +24 -0
  79. package/crates/naome-core/src/verification.rs +29 -18
  80. package/crates/naome-core/src/workflow/agent/capability.rs +194 -0
  81. package/crates/naome-core/src/workflow/agent/context_delta.rs +42 -0
  82. package/crates/naome-core/src/workflow/agent/decision.rs +32 -0
  83. package/crates/naome-core/src/workflow/agent/execution.rs +80 -0
  84. package/crates/naome-core/src/workflow/agent/proof.rs +24 -0
  85. package/crates/naome-core/src/workflow/agent/support.rs +58 -0
  86. package/crates/naome-core/src/workflow/agent/watchdog.rs +47 -0
  87. package/crates/naome-core/src/workflow/agent.rs +34 -0
  88. package/crates/naome-core/src/workflow/agent_types.rs +105 -0
  89. package/crates/naome-core/src/workflow/doctor.rs +39 -0
  90. package/crates/naome-core/src/workflow/mod.rs +11 -0
  91. package/crates/naome-core/src/workflow/output.rs +8 -2
  92. package/crates/naome-core/src/workflow/phase_inference.rs +1 -1
  93. package/crates/naome-core/tests/context.rs +99 -0
  94. package/crates/naome-core/tests/harness_health.rs +33 -40
  95. package/crates/naome-core/tests/install_plan.rs +12 -0
  96. package/crates/naome-core/tests/quality.rs +178 -2
  97. package/crates/naome-core/tests/quality_performance.rs +39 -2
  98. package/crates/naome-core/tests/quality_structure_adapters.rs +39 -0
  99. package/crates/naome-core/tests/repo_support/mod.rs +7 -1
  100. package/crates/naome-core/tests/repo_support/verification_values.rs +148 -1
  101. package/crates/naome-core/tests/repository_model.rs +281 -0
  102. package/crates/naome-core/tests/route_user_diff.rs +49 -1
  103. package/crates/naome-core/tests/semantic_legacy.rs +72 -38
  104. package/crates/naome-core/tests/task_ledger.rs +328 -0
  105. package/crates/naome-core/tests/task_state.rs +34 -14
  106. package/crates/naome-core/tests/task_state_support/mod.rs +2 -1
  107. package/crates/naome-core/tests/task_state_support/states.rs +28 -11
  108. package/crates/naome-core/tests/verification.rs +14 -39
  109. package/crates/naome-core/tests/verification_contract.rs +6 -52
  110. package/crates/naome-core/tests/workflow_agent.rs +233 -0
  111. package/crates/naome-core/tests/workflow_agent_support/mod.rs +159 -0
  112. package/crates/naome-core/tests/workflow_doctor.rs +21 -0
  113. package/crates/naome-core/tests/workflow_integrity.rs +2 -20
  114. package/crates/naome-core/tests/workflow_support/mod.rs +59 -20
  115. package/installer/codex-hooks.js +121 -0
  116. package/installer/context.js +10 -0
  117. package/installer/filesystem.js +4 -0
  118. package/installer/flows.js +8 -4
  119. package/installer/harness-files.js +6 -0
  120. package/installer/install-plan.js +4 -0
  121. package/installer/main.js +1 -1
  122. package/installer/native.js +1 -1
  123. package/native/darwin-arm64/naome +0 -0
  124. package/native/linux-x64/naome +0 -0
  125. package/package.json +1 -1
  126. package/templates/naome-root/.codex/config.toml +2 -0
  127. package/templates/naome-root/.codex/hooks.json +70 -0
  128. package/templates/naome-root/.naome/bin/check-harness-health.js +8 -6
  129. package/templates/naome-root/.naome/bin/check-task-state.js +12 -7
  130. package/templates/naome-root/.naome/bin/codex-hook-io.js +122 -0
  131. package/templates/naome-root/.naome/bin/codex-hook-policy.js +180 -0
  132. package/templates/naome-root/.naome/bin/codex-hook-runtime.js +174 -0
  133. package/templates/naome-root/.naome/bin/codex-hook.js +6 -0
  134. package/templates/naome-root/.naome/bin/naome.js +35 -4
  135. package/templates/naome-root/.naome/manifest.json +12 -6
  136. package/templates/naome-root/.naome/repository-model.json +6 -0
  137. package/templates/naome-root/.naome/repository-quality.json +3 -1
  138. package/templates/naome-root/.naome/verification.json +15 -1
  139. package/templates/naome-root/AGENTS.md +38 -83
  140. package/templates/naome-root/docs/naome/agent-workflow.md +54 -18
  141. package/templates/naome-root/docs/naome/codex-hooks.md +82 -0
  142. package/templates/naome-root/docs/naome/context-economy.md +73 -0
  143. package/templates/naome-root/docs/naome/first-run.md +25 -14
  144. package/templates/naome-root/docs/naome/index.md +18 -10
  145. package/templates/naome-root/docs/naome/repository-model.md +92 -0
  146. package/templates/naome-root/docs/naome/repository-quality.md +47 -7
  147. package/templates/naome-root/docs/naome/repository-structure.md +10 -3
  148. package/templates/naome-root/docs/naome/task-ledger.md +71 -0
  149. 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
- classify_mutations, doctor_report, refresh_integrity, safe_rg_args, tracked_process_report,
5
- validate_search_command, verification_phase_plan,
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()
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-core"
3
- version = "1.3.0"
3
+ version = "1.3.2"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ mod helpers;
2
+ mod select;
3
+ mod types;
4
+
5
+ pub use select::{select_context_for_changed_paths, select_context_for_prompt};
6
+ pub use types::{ContextBudgetLedger, ContextCapsule, ContextItem, ContextSelection};
@@ -161,5 +161,5 @@ fn has_completed_task_owned_paths(task: Option<&TaskDecision>, changed_paths: &[
161
161
  }
162
162
 
163
163
  fn is_control_state_path(path: &str) -> bool {
164
- path == ".naome/task-state.json"
164
+ path == ".naome/task-state.json" || path.starts_with(".naome/tasks/")
165
165
  }
@@ -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 = read_json(root, ".naome/task-state.json")?;
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
+ );