@lamentis/naome 1.2.1 → 1.3.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.
Files changed (155) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +117 -47
  3. package/bin/naome.js +65 -12
  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 +12 -2
  7. package/crates/naome-cli/src/main.rs +78 -29
  8. package/crates/naome-cli/src/quality_commands.rs +238 -34
  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 +120 -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/git.rs +4 -2
  22. package/crates/naome-core/src/install_plan.rs +20 -0
  23. package/crates/naome-core/src/journal.rs +2 -7
  24. package/crates/naome-core/src/lib.rs +35 -8
  25. package/crates/naome-core/src/quality/adapter_ios.rs +131 -0
  26. package/crates/naome-core/src/quality/adapter_support.rs +67 -0
  27. package/crates/naome-core/src/quality/adapters.rs +81 -18
  28. package/crates/naome-core/src/quality/baseline.rs +8 -0
  29. package/crates/naome-core/src/quality/cache.rs +151 -0
  30. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +19 -8
  31. package/crates/naome-core/src/quality/checks/near_duplicates.rs +4 -2
  32. package/crates/naome-core/src/quality/checks.rs +7 -8
  33. package/crates/naome-core/src/quality/cleanup.rs +36 -3
  34. package/crates/naome-core/src/quality/config.rs +21 -3
  35. package/crates/naome-core/src/quality/mod.rs +189 -10
  36. package/crates/naome-core/src/quality/reconcile.rs +138 -0
  37. package/crates/naome-core/src/quality/reconcile_anchors.rs +64 -0
  38. package/crates/naome-core/src/quality/scanner/analysis/normalize.rs +78 -0
  39. package/crates/naome-core/src/quality/scanner/analysis.rs +175 -0
  40. package/crates/naome-core/src/quality/scanner/repo_paths.rs +39 -3
  41. package/crates/naome-core/src/quality/scanner.rs +235 -217
  42. package/crates/naome-core/src/quality/semantic/checks.rs +151 -0
  43. package/crates/naome-core/src/quality/semantic/extract.rs +158 -0
  44. package/crates/naome-core/src/quality/semantic/model.rs +85 -0
  45. package/crates/naome-core/src/quality/semantic/route.rs +52 -0
  46. package/crates/naome-core/src/quality/semantic.rs +68 -0
  47. package/crates/naome-core/src/quality/structure/adapter_ios.rs +149 -0
  48. package/crates/naome-core/src/quality/structure/adapters.rs +60 -42
  49. package/crates/naome-core/src/quality/structure/checks/directory.rs +13 -21
  50. package/crates/naome-core/src/quality/structure/checks.rs +1 -1
  51. package/crates/naome-core/src/quality/structure/classify/roles.rs +51 -5
  52. package/crates/naome-core/src/quality/structure/classify.rs +52 -0
  53. package/crates/naome-core/src/quality/structure/config.rs +24 -3
  54. package/crates/naome-core/src/quality/structure/mod.rs +5 -2
  55. package/crates/naome-core/src/quality/structure/model.rs +8 -1
  56. package/crates/naome-core/src/quality/types.rs +59 -2
  57. package/crates/naome-core/src/repository_model/detect.rs +188 -0
  58. package/crates/naome-core/src/repository_model/explain.rs +121 -0
  59. package/crates/naome-core/src/repository_model/path_scan.rs +67 -0
  60. package/crates/naome-core/src/repository_model/path_support.rs +59 -0
  61. package/crates/naome-core/src/repository_model/types.rs +152 -0
  62. package/crates/naome-core/src/repository_model/world.rs +48 -0
  63. package/crates/naome-core/src/repository_model/world_adapters.rs +145 -0
  64. package/crates/naome-core/src/repository_model/world_path_facts.rs +55 -0
  65. package/crates/naome-core/src/repository_model/world_paths.rs +168 -0
  66. package/crates/naome-core/src/repository_model.rs +164 -0
  67. package/crates/naome-core/src/route/builtin_checks.rs +41 -16
  68. package/crates/naome-core/src/task_ledger/import.rs +142 -0
  69. package/crates/naome-core/src/task_ledger/model.rs +13 -0
  70. package/crates/naome-core/src/task_ledger/proof_record.rs +52 -0
  71. package/crates/naome-core/src/task_ledger/read.rs +118 -0
  72. package/crates/naome-core/src/task_ledger/render.rs +55 -0
  73. package/crates/naome-core/src/task_ledger/write.rs +38 -0
  74. package/crates/naome-core/src/task_ledger.rs +48 -0
  75. package/crates/naome-core/src/task_state/api.rs +4 -2
  76. package/crates/naome-core/src/task_state/completed_refresh.rs +5 -16
  77. package/crates/naome-core/src/task_state/diff.rs +2 -2
  78. package/crates/naome-core/src/task_state/evidence.rs +8 -3
  79. package/crates/naome-core/src/task_state/mod.rs +1 -1
  80. package/crates/naome-core/src/task_state/progress.rs +13 -0
  81. package/crates/naome-core/src/task_state/proof_model.rs +8 -8
  82. package/crates/naome-core/src/task_state/repair.rs +2 -2
  83. package/crates/naome-core/src/task_state/task_diff_api.rs +9 -18
  84. package/crates/naome-core/src/task_state/types.rs +24 -0
  85. package/crates/naome-core/src/verification.rs +29 -18
  86. package/crates/naome-core/src/workflow/agent/capability.rs +194 -0
  87. package/crates/naome-core/src/workflow/agent/context_delta.rs +42 -0
  88. package/crates/naome-core/src/workflow/agent/decision.rs +32 -0
  89. package/crates/naome-core/src/workflow/agent/execution.rs +80 -0
  90. package/crates/naome-core/src/workflow/agent/proof.rs +24 -0
  91. package/crates/naome-core/src/workflow/agent/support.rs +58 -0
  92. package/crates/naome-core/src/workflow/agent/watchdog.rs +47 -0
  93. package/crates/naome-core/src/workflow/agent.rs +34 -0
  94. package/crates/naome-core/src/workflow/agent_types.rs +105 -0
  95. package/crates/naome-core/src/workflow/doctor.rs +183 -0
  96. package/crates/naome-core/src/workflow/mod.rs +13 -0
  97. package/crates/naome-core/src/workflow/mutation.rs +1 -2
  98. package/crates/naome-core/src/workflow/output.rs +8 -2
  99. package/crates/naome-core/src/workflow/phase_inference.rs +1 -1
  100. package/crates/naome-core/tests/context.rs +99 -0
  101. package/crates/naome-core/tests/harness_health.rs +4 -0
  102. package/crates/naome-core/tests/install_plan.rs +14 -0
  103. package/crates/naome-core/tests/quality.rs +190 -5
  104. package/crates/naome-core/tests/quality_performance.rs +268 -0
  105. package/crates/naome-core/tests/quality_structure_adapters.rs +39 -0
  106. package/crates/naome-core/tests/quality_structure_policy.rs +19 -0
  107. package/crates/naome-core/tests/repo_support/mod.rs +5 -1
  108. package/crates/naome-core/tests/repo_support/verification_values.rs +55 -0
  109. package/crates/naome-core/tests/repository_model.rs +281 -0
  110. package/crates/naome-core/tests/route_user_diff.rs +59 -7
  111. package/crates/naome-core/tests/semantic_legacy.rs +174 -0
  112. package/crates/naome-core/tests/task_ledger.rs +328 -0
  113. package/crates/naome-core/tests/task_state.rs +28 -0
  114. package/crates/naome-core/tests/verification.rs +29 -36
  115. package/crates/naome-core/tests/workflow_agent.rs +233 -0
  116. package/crates/naome-core/tests/workflow_agent_support/mod.rs +159 -0
  117. package/crates/naome-core/tests/workflow_doctor.rs +45 -0
  118. package/crates/naome-core/tests/workflow_policy.rs +6 -1
  119. package/installer/codex-hooks.js +121 -0
  120. package/installer/context.js +10 -0
  121. package/installer/filesystem.js +4 -0
  122. package/installer/flows.js +8 -4
  123. package/installer/git-boundary.js +1 -0
  124. package/installer/harness-files.js +6 -0
  125. package/installer/install-plan.js +4 -0
  126. package/installer/main.js +1 -1
  127. package/installer/native.js +1 -1
  128. package/native/darwin-arm64/naome +0 -0
  129. package/native/linux-x64/naome +0 -0
  130. package/package.json +1 -1
  131. package/templates/naome-root/.codex/config.toml +2 -0
  132. package/templates/naome-root/.codex/hooks.json +70 -0
  133. package/templates/naome-root/.naome/bin/check-harness-health.js +8 -6
  134. package/templates/naome-root/.naome/bin/check-task-state.js +12 -7
  135. package/templates/naome-root/.naome/bin/codex-hook-io.js +122 -0
  136. package/templates/naome-root/.naome/bin/codex-hook-policy.js +180 -0
  137. package/templates/naome-root/.naome/bin/codex-hook-runtime.js +174 -0
  138. package/templates/naome-root/.naome/bin/codex-hook.js +6 -0
  139. package/templates/naome-root/.naome/bin/naome.js +45 -7
  140. package/templates/naome-root/.naome/manifest.json +12 -6
  141. package/templates/naome-root/.naome/repository-model.json +6 -0
  142. package/templates/naome-root/.naome/repository-quality.json +3 -1
  143. package/templates/naome-root/.naome/verification.json +15 -1
  144. package/templates/naome-root/.naomeignore +1 -0
  145. package/templates/naome-root/AGENTS.md +38 -83
  146. package/templates/naome-root/docs/naome/agent-workflow.md +66 -28
  147. package/templates/naome-root/docs/naome/codex-hooks.md +82 -0
  148. package/templates/naome-root/docs/naome/context-economy.md +73 -0
  149. package/templates/naome-root/docs/naome/first-run.md +25 -14
  150. package/templates/naome-root/docs/naome/index.md +18 -10
  151. package/templates/naome-root/docs/naome/repository-model.md +92 -0
  152. package/templates/naome-root/docs/naome/repository-quality.md +104 -5
  153. package/templates/naome-root/docs/naome/repository-structure.md +10 -3
  154. package/templates/naome-root/docs/naome/task-ledger.md +71 -0
  155. package/templates/naome-root/docs/naome/testing.md +16 -3
@@ -6,7 +6,7 @@ use serde_json::Value;
6
6
  use crate::install_plan::MACHINE_OWNED_PATHS;
7
7
  use crate::models::NaomeError;
8
8
 
9
- use super::types::CONTROL_STATE_PATH;
9
+ use super::types::is_control_state_path;
10
10
  use super::util::{
11
11
  is_non_empty_string, matches_any_pattern, normalize_path, read_json, string_array,
12
12
  };
@@ -69,7 +69,7 @@ pub(super) fn is_completed_task_diff(task_state: &Value, changed_paths: &[String
69
69
  let allowed_paths = string_array(active_task.get("allowedPaths")).unwrap_or_default();
70
70
  let task_paths: Vec<&String> = changed_paths
71
71
  .iter()
72
- .filter(|path| path.as_str() != CONTROL_STATE_PATH)
72
+ .filter(|path| !is_control_state_path(path))
73
73
  .collect();
74
74
  !task_paths.is_empty()
75
75
  && task_paths
@@ -16,10 +16,10 @@ use super::shape::{
16
16
  validate_pending_upgrade, validate_required_check_ids, validate_task_state_shape,
17
17
  };
18
18
  use super::types::{
19
- CompletedTaskCommitDiff, HarnessRefreshDiff, HarnessRefreshWithUnrelatedDiff, TaskStateOptions,
20
- CONTROL_STATE_PATH,
19
+ is_control_state_path, read_complete_active_task, CompletedTaskCommitDiff, HarnessRefreshDiff,
20
+ HarnessRefreshWithUnrelatedDiff, TaskStateOptions,
21
21
  };
22
- use super::util::{matches_any_pattern, read_json, string_array};
22
+ use super::util::{matches_any_pattern, string_array};
23
23
 
24
24
  pub fn completed_task_commit_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
25
25
  Ok(completed_task_commit_diff(root)?
@@ -30,16 +30,7 @@ pub fn completed_task_commit_paths(root: &Path) -> Result<Vec<String>, NaomeErro
30
30
  pub fn completed_task_commit_diff(
31
31
  root: &Path,
32
32
  ) -> Result<Option<CompletedTaskCommitDiff>, NaomeError> {
33
- let mut read_errors = Vec::new();
34
- let Some(task_state) = read_json(root, ".naome/task-state.json", &mut read_errors)? else {
35
- return Ok(None);
36
- };
37
- if !read_errors.is_empty()
38
- || task_state.get("status").and_then(Value::as_str) != Some("complete")
39
- {
40
- return Ok(None);
41
- }
42
- let Some(active_task) = task_state.get("activeTask") else {
33
+ let Some((task_state, active_task)) = read_complete_active_task(root)? else {
43
34
  return Ok(None);
44
35
  };
45
36
 
@@ -47,7 +38,7 @@ pub fn completed_task_commit_diff(
47
38
  let mut task_entries = Vec::new();
48
39
  let mut unrelated_paths = Vec::new();
49
40
  for entry in read_git_changed_entries(root)? {
50
- if entry.path == CONTROL_STATE_PATH || matches_any_pattern(&entry.path, &allowed_paths) {
41
+ if is_control_state_path(&entry.path) || matches_any_pattern(&entry.path, &allowed_paths) {
51
42
  task_entries.push(entry);
52
43
  } else {
53
44
  unrelated_paths.push(entry.path);
@@ -60,16 +51,16 @@ pub fn completed_task_commit_diff(
60
51
 
61
52
  let mut errors = Vec::new();
62
53
  validate_task_state_shape(&task_state, &mut errors);
63
- validate_active_task(Some(active_task), &mut errors);
54
+ validate_active_task(Some(&active_task), &mut errors);
64
55
  validate_pending_upgrade(&task_state, root, &mut errors)?;
65
- validate_active_task_references(Some(active_task), root, &mut errors, Some("complete"))?;
56
+ validate_active_task_references(Some(&active_task), root, &mut errors, Some("complete"))?;
66
57
  if !task_state.get("blocker").is_some_and(Value::is_null) {
67
58
  errors.push("complete task state must have blocker set to null.".to_string());
68
59
  }
69
60
  let check_ids = read_verification_check_ids(root, &mut errors)?;
70
- validate_required_check_ids(active_task, &check_ids, &mut errors);
61
+ validate_required_check_ids(&active_task, &check_ids, &mut errors);
71
62
  validate_complete_task_against_entries(
72
- active_task,
63
+ &active_task,
73
64
  root,
74
65
  &check_ids,
75
66
  &task_entries,
@@ -1,6 +1,13 @@
1
+ use std::path::Path;
2
+
3
+ use serde_json::Value;
4
+
1
5
  use crate::harness_health::HarnessHealthOptions;
6
+ use crate::models::NaomeError;
7
+ use crate::task_ledger::read_task_state_projection;
2
8
 
3
9
  pub(super) const CONTROL_STATE_PATH: &str = ".naome/task-state.json";
10
+ pub(super) const TASK_LEDGER_PATH_PREFIX: &str = ".naome/tasks/";
4
11
  pub(super) const ALLOWED_STATUS: &[&str] = &[
5
12
  "idle",
6
13
  "planning",
@@ -85,3 +92,20 @@ pub(super) struct TaskDiff {
85
92
  pub(super) diff_paths: Vec<String>,
86
93
  pub(super) outside_paths: Vec<String>,
87
94
  }
95
+
96
+ pub(super) fn is_control_state_path(path: &str) -> bool {
97
+ path == CONTROL_STATE_PATH || path.starts_with(TASK_LEDGER_PATH_PREFIX)
98
+ }
99
+
100
+ pub(super) fn read_complete_active_task(root: &Path) -> Result<Option<(Value, Value)>, NaomeError> {
101
+ let Some(task_state) = read_task_state_projection(root)? else {
102
+ return Ok(None);
103
+ };
104
+ if task_state.get("status").and_then(Value::as_str) != Some("complete") {
105
+ return Ok(None);
106
+ }
107
+ let Some(active_task) = task_state.get("activeTask") else {
108
+ return Ok(None);
109
+ };
110
+ Ok(Some((task_state.clone(), active_task.clone())))
111
+ }
@@ -46,7 +46,7 @@ pub fn seed_builtin_verification_checks(root: &Path) -> Result<bool, NaomeError>
46
46
  }
47
47
  }
48
48
 
49
- let wired_change_types = wire_repository_quality_check(verification_object);
49
+ let wired_change_types = wire_repository_gate_checks(verification_object);
50
50
  let phases_added = ensure_default_phases(verification_object);
51
51
 
52
52
  if missing_checks.is_empty() && !wired_change_types && !phases_added {
@@ -126,7 +126,7 @@ fn default_phases() -> Vec<BuiltinPhase> {
126
126
  BuiltinPhase {
127
127
  id: "quality",
128
128
  order: 20,
129
- check_ids: &["repository-quality-check"],
129
+ check_ids: &["repository-quality-check", "repository-semantic-check"],
130
130
  },
131
131
  BuiltinPhase {
132
132
  id: "diff-check",
@@ -136,7 +136,7 @@ fn default_phases() -> Vec<BuiltinPhase> {
136
136
  ]
137
137
  }
138
138
 
139
- fn wire_repository_quality_check(verification_object: &mut serde_json::Map<String, Value>) -> bool {
139
+ fn wire_repository_gate_checks(verification_object: &mut serde_json::Map<String, Value>) -> bool {
140
140
  let Some(change_types) = verification_object
141
141
  .get_mut("changeTypes")
142
142
  .and_then(Value::as_array_mut)
@@ -155,33 +155,35 @@ fn wire_repository_quality_check(verification_object: &mut serde_json::Map<Strin
155
155
  {
156
156
  continue;
157
157
  }
158
- let already_wired = ["requiredChecks", "recommendedChecks"].iter().any(|field| {
159
- change_type_object
160
- .get(*field)
161
- .and_then(Value::as_array)
162
- .is_some_and(|checks| {
163
- checks
164
- .iter()
165
- .any(|check| check.as_str() == Some("repository-quality-check"))
166
- })
167
- });
168
- if already_wired {
169
- continue;
170
- }
171
158
  if !change_type_object
172
159
  .get("requiredChecks")
173
160
  .is_some_and(Value::is_array)
174
161
  {
175
162
  change_type_object.insert("requiredChecks".to_string(), Value::Array(Vec::new()));
176
163
  }
164
+ let missing_checks = ["repository-quality-check", "repository-semantic-check"]
165
+ .into_iter()
166
+ .filter(|check_id| {
167
+ !["requiredChecks", "recommendedChecks"].iter().any(|field| {
168
+ change_type_object
169
+ .get(*field)
170
+ .and_then(Value::as_array)
171
+ .is_some_and(|checks| {
172
+ checks.iter().any(|check| check.as_str() == Some(*check_id))
173
+ })
174
+ })
175
+ })
176
+ .collect::<Vec<_>>();
177
177
  let Some(required_checks) = change_type_object
178
178
  .get_mut("requiredChecks")
179
179
  .and_then(Value::as_array_mut)
180
180
  else {
181
181
  continue;
182
182
  };
183
- required_checks.push(Value::String("repository-quality-check".to_string()));
184
- changed = true;
183
+ for check_id in missing_checks {
184
+ required_checks.push(Value::String(check_id.to_string()));
185
+ changed = true;
186
+ }
185
187
  }
186
188
 
187
189
  changed
@@ -247,6 +249,15 @@ fn builtin_checks() -> Vec<BuiltinCheck> {
247
249
  ".naome/repository-quality-baseline.json",
248
250
  ],
249
251
  },
252
+ BuiltinCheck {
253
+ id: "repository-semantic-check",
254
+ command: "node .naome/bin/naome.js semantic check --changed",
255
+ purpose: "Validate changed files against deterministic NAOME semantic cleanup rules.",
256
+ evidence: &[
257
+ ".naome/repository-quality.json",
258
+ ".naome/repository-quality-baseline.json",
259
+ ],
260
+ },
250
261
  ]
251
262
  }
252
263
 
@@ -0,0 +1,194 @@
1
+ use std::fs;
2
+ use std::path::Path;
3
+
4
+ use serde_json::Value;
5
+
6
+ use crate::models::NaomeError;
7
+ use crate::repository_model::refresh_repository_model;
8
+ use crate::workflow::agent_types::{
9
+ CapabilityGraph, CapabilityRoot, RepositoryCapability, VerificationEdge,
10
+ };
11
+
12
+ pub fn repository_capability_graph(root: &Path) -> Result<CapabilityGraph, NaomeError> {
13
+ let mut model = refresh_repository_model(root, false)?.model;
14
+ if model.languages.is_empty() && model.build_systems.is_empty() && model.adapters.is_empty() {
15
+ model = fallback_capability_model(root)?;
16
+ }
17
+ let mut capabilities = Vec::new();
18
+ capabilities.extend(
19
+ model
20
+ .languages
21
+ .into_iter()
22
+ .map(|signal| capability_from_signal("language", signal)),
23
+ );
24
+ capabilities.extend(
25
+ model
26
+ .build_systems
27
+ .into_iter()
28
+ .map(|signal| capability_from_signal("build", signal)),
29
+ );
30
+ capabilities.extend(
31
+ model
32
+ .adapters
33
+ .into_iter()
34
+ .map(|signal| capability_from_signal("adapter", signal)),
35
+ );
36
+ capabilities.sort_by(|left, right| left.id.cmp(&right.id));
37
+
38
+ let verification_edges = if model.verification_checks.is_empty() {
39
+ read_verification_edges(root)?
40
+ } else {
41
+ model
42
+ .verification_checks
43
+ .into_iter()
44
+ .map(|check| VerificationEdge {
45
+ check_id: check.id,
46
+ command: check.command,
47
+ evidence: check.evidence,
48
+ })
49
+ .collect()
50
+ };
51
+
52
+ Ok(CapabilityGraph {
53
+ schema: "naome.capability-graph.v1".to_string(),
54
+ capabilities,
55
+ roots: model
56
+ .roots
57
+ .into_iter()
58
+ .map(|root| CapabilityRoot {
59
+ role: root.role,
60
+ path: root.path,
61
+ })
62
+ .collect(),
63
+ verification_edges,
64
+ })
65
+ }
66
+
67
+ fn capability_from_signal(
68
+ kind: &str,
69
+ signal: crate::RepositoryWorldSignal,
70
+ ) -> RepositoryCapability {
71
+ RepositoryCapability {
72
+ id: format!("{kind}:{}", signal.value),
73
+ kind: kind.to_string(),
74
+ value: signal.value,
75
+ evidence: signal.evidence,
76
+ }
77
+ }
78
+
79
+ fn fallback_capability_model(root: &Path) -> Result<crate::RepositoryModel, NaomeError> {
80
+ let paths = fallback_paths(root)?;
81
+ let mut model = crate::RepositoryModel::default();
82
+ model.schema = "naome.repository-model.v2".to_string();
83
+ model.version = 2;
84
+ if paths
85
+ .iter()
86
+ .any(|path| path == "Cargo.toml" || path.ends_with(".rs"))
87
+ {
88
+ let evidence = first_evidence(&paths, &["Cargo.toml", ".rs"]);
89
+ model
90
+ .languages
91
+ .push(signal("language:rust", "rust", &evidence));
92
+ model
93
+ .build_systems
94
+ .push(signal("buildSystem:cargo", "cargo", &evidence));
95
+ }
96
+ Ok(model)
97
+ }
98
+
99
+ fn signal(id: &str, value: &str, evidence: &[String]) -> crate::RepositoryWorldSignal {
100
+ crate::RepositoryWorldSignal {
101
+ id: id.to_string(),
102
+ value: value.to_string(),
103
+ confidence: "detected".to_string(),
104
+ source: "fallback-scan".to_string(),
105
+ evidence: evidence.to_vec(),
106
+ }
107
+ }
108
+
109
+ fn read_verification_edges(root: &Path) -> Result<Vec<VerificationEdge>, NaomeError> {
110
+ let path = root.join(".naome/verification.json");
111
+ if !path.is_file() {
112
+ return Ok(Vec::new());
113
+ }
114
+ let verification: Value = serde_json::from_str(&fs::read_to_string(path)?)?;
115
+ Ok(verification
116
+ .get("checks")
117
+ .and_then(Value::as_array)
118
+ .into_iter()
119
+ .flatten()
120
+ .filter_map(|check| {
121
+ Some(VerificationEdge {
122
+ check_id: check.get("id")?.as_str()?.to_string(),
123
+ command: check.get("command")?.as_str()?.to_string(),
124
+ evidence: check
125
+ .get("evidence")
126
+ .and_then(Value::as_array)
127
+ .into_iter()
128
+ .flatten()
129
+ .filter_map(Value::as_str)
130
+ .map(ToString::to_string)
131
+ .collect(),
132
+ })
133
+ })
134
+ .collect())
135
+ }
136
+
137
+ fn fallback_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
138
+ let mut paths = Vec::new();
139
+ collect_fallback_paths(root, root, &mut paths)?;
140
+ paths.sort();
141
+ paths.dedup();
142
+ Ok(paths)
143
+ }
144
+
145
+ fn collect_fallback_paths(
146
+ root: &Path,
147
+ dir: &Path,
148
+ paths: &mut Vec<String>,
149
+ ) -> Result<(), NaomeError> {
150
+ for entry in fs::read_dir(dir)? {
151
+ let entry = entry?;
152
+ let path = entry.path();
153
+ let relative = path
154
+ .strip_prefix(root)
155
+ .unwrap_or(&path)
156
+ .to_string_lossy()
157
+ .replace('\\', "/");
158
+ if is_fallback_ignored(&relative) {
159
+ continue;
160
+ }
161
+ if path.is_dir() {
162
+ collect_fallback_paths(root, &path, paths)?;
163
+ } else if path.is_file() {
164
+ paths.push(relative);
165
+ }
166
+ }
167
+ Ok(())
168
+ }
169
+
170
+ fn is_fallback_ignored(path: &str) -> bool {
171
+ path == ".git"
172
+ || path.starts_with(".git/")
173
+ || path.starts_with(".naome/archive/")
174
+ || path.starts_with(".naome/cache/")
175
+ || path == "node_modules"
176
+ || path.starts_with("node_modules/")
177
+ || path == "target"
178
+ || path.starts_with("target/")
179
+ }
180
+
181
+ fn first_evidence(paths: &[String], markers: &[&str]) -> Vec<String> {
182
+ let mut evidence = Vec::new();
183
+ for marker in markers {
184
+ if let Some(path) = paths
185
+ .iter()
186
+ .find(|path| path.as_str() == *marker || path.ends_with(marker))
187
+ {
188
+ evidence.push(path.clone());
189
+ }
190
+ }
191
+ evidence.sort();
192
+ evidence.dedup();
193
+ evidence
194
+ }
@@ -0,0 +1,42 @@
1
+ use std::collections::BTreeSet;
2
+ use std::path::Path;
3
+
4
+ use crate::models::NaomeError;
5
+
6
+ use super::support::{changed_paths, normalize_path, required_context};
7
+ use crate::workflow::agent_types::ContextDeltaReport;
8
+
9
+ pub fn context_delta_report(
10
+ root: &Path,
11
+ read_paths: &[String],
12
+ ) -> Result<ContextDeltaReport, NaomeError> {
13
+ let changed = changed_paths(root)?.into_iter().collect::<BTreeSet<_>>();
14
+ let required = required_context(root);
15
+ let read = read_paths
16
+ .iter()
17
+ .map(|path| normalize_path(path))
18
+ .collect::<BTreeSet<_>>();
19
+ let reusable_context = read
20
+ .iter()
21
+ .filter(|path| !changed.contains(*path) && root.join(path.as_str()).exists())
22
+ .cloned()
23
+ .collect();
24
+ let stale_context = read
25
+ .iter()
26
+ .filter(|path| changed.contains(*path))
27
+ .cloned()
28
+ .collect();
29
+ let unread_required_context = required
30
+ .iter()
31
+ .filter(|path| !read.contains(*path))
32
+ .cloned()
33
+ .collect();
34
+
35
+ Ok(ContextDeltaReport {
36
+ schema: "naome.context-delta.v1".to_string(),
37
+ reusable_context,
38
+ stale_context,
39
+ unread_required_context,
40
+ reason_codes: vec!["read-set-minimization".to_string()],
41
+ })
42
+ }
@@ -0,0 +1,32 @@
1
+ use std::path::Path;
2
+
3
+ use crate::decision::{evaluate_decision, EvaluationOptions};
4
+ use crate::models::NaomeError;
5
+ use crate::workflow::agent_types::DecisionGate;
6
+
7
+ pub fn decision_gate(root: &Path) -> Result<DecisionGate, NaomeError> {
8
+ let decision = evaluate_decision(root, EvaluationOptions::offline())?;
9
+ let action = if decision.blocked {
10
+ if decision.state == "active_task_blocked" {
11
+ "ask_human"
12
+ } else {
13
+ "stop"
14
+ }
15
+ } else if decision.state == "ready_for_task" {
16
+ "create_task"
17
+ } else {
18
+ "continue"
19
+ };
20
+ let mut reason_codes = vec![decision.state.clone()];
21
+ if decision.state == "active_task_blocked" {
22
+ reason_codes.push("scope_review_required".to_string());
23
+ }
24
+
25
+ Ok(DecisionGate {
26
+ schema: "naome.decision-gate.v1".to_string(),
27
+ action: action.to_string(),
28
+ reason_codes,
29
+ human_options: decision.human_options,
30
+ next_action: decision.next_action,
31
+ })
32
+ }
@@ -0,0 +1,80 @@
1
+ use std::path::Path;
2
+
3
+ use serde_json::Value;
4
+
5
+ use crate::models::NaomeError;
6
+ use crate::workflow::agent_types::{ExecutionPlanLedger, ExecutionPlanStep};
7
+
8
+ use super::support::{read_task_state, required_context};
9
+
10
+ pub fn execution_plan(
11
+ root: &Path,
12
+ changed_paths: &[String],
13
+ ) -> Result<ExecutionPlanLedger, NaomeError> {
14
+ let task_state = read_task_state(root)?;
15
+ let status = task_state
16
+ .get("status")
17
+ .and_then(Value::as_str)
18
+ .unwrap_or("invalid")
19
+ .to_string();
20
+ let mut steps = vec![read_context_step(root)];
21
+
22
+ let task_paths = changed_paths
23
+ .iter()
24
+ .filter(|path| path.as_str() != ".naome/task-state.json")
25
+ .cloned()
26
+ .collect::<Vec<_>>();
27
+ if !task_paths.is_empty() {
28
+ steps.push(path_quality_step(&task_paths));
29
+ }
30
+ steps.push(task_state_step(&status));
31
+
32
+ Ok(ExecutionPlanLedger {
33
+ task_state: status,
34
+ changed_paths: changed_paths.to_vec(),
35
+ steps,
36
+ })
37
+ }
38
+
39
+ fn read_context_step(root: &Path) -> ExecutionPlanStep {
40
+ ExecutionPlanStep {
41
+ id: "read-required-context".to_string(),
42
+ phase: "shape".to_string(),
43
+ paths: required_context(root),
44
+ commands: Vec::new(),
45
+ reason_codes: vec!["token-economy".to_string()],
46
+ }
47
+ }
48
+
49
+ fn path_quality_step(changed_paths: &[String]) -> ExecutionPlanStep {
50
+ ExecutionPlanStep {
51
+ id: "run-path-quality".to_string(),
52
+ phase: "quality".to_string(),
53
+ paths: changed_paths.to_vec(),
54
+ commands: changed_paths
55
+ .iter()
56
+ .map(|path| format!("node .naome/bin/naome.js quality check --path {path}"))
57
+ .collect(),
58
+ reason_codes: vec!["early-touched-file-gate".to_string()],
59
+ }
60
+ }
61
+
62
+ fn task_state_step(status: &str) -> ExecutionPlanStep {
63
+ let implementing = status == "implementing";
64
+ ExecutionPlanStep {
65
+ id: if implementing {
66
+ "run-progress-gate"
67
+ } else {
68
+ "run-admission-or-status"
69
+ }
70
+ .to_string(),
71
+ phase: "shape".to_string(),
72
+ paths: vec![".naome/task-state.json".to_string()],
73
+ commands: vec![if implementing {
74
+ "node .naome/bin/check-task-state.js --progress".to_string()
75
+ } else {
76
+ "node .naome/bin/check-task-state.js --admission".to_string()
77
+ }],
78
+ reason_codes: vec!["scope-drift-prevention".to_string()],
79
+ }
80
+ }
@@ -0,0 +1,24 @@
1
+ use std::path::Path;
2
+
3
+ use crate::models::NaomeError;
4
+
5
+ use super::support::changed_paths;
6
+ use crate::workflow::agent_types::ProofPlan;
7
+ use crate::workflow::phases::{verification_phase_plan, CommandCheckResult};
8
+
9
+ pub fn proof_plan_for_task(
10
+ root: &Path,
11
+ completed: &[CommandCheckResult],
12
+ ) -> Result<ProofPlan, NaomeError> {
13
+ let phase_plan = verification_phase_plan(root, completed)?;
14
+ Ok(ProofPlan {
15
+ schema: "naome.proof-plan.v1".to_string(),
16
+ recommended_check_ids: phase_plan.recommended_check_ids,
17
+ withheld_check_ids: phase_plan.withheld_check_ids,
18
+ evidence_paths: changed_paths(root)?
19
+ .into_iter()
20
+ .filter(|path| path != ".naome/task-state.json")
21
+ .collect(),
22
+ reason_codes: vec!["phase-ordered-minimal-proof".to_string()],
23
+ })
24
+ }
@@ -0,0 +1,58 @@
1
+ use std::collections::BTreeSet;
2
+ use std::path::Path;
3
+
4
+ use serde_json::Value;
5
+
6
+ use crate::git;
7
+ use crate::models::NaomeError;
8
+ use crate::task_ledger::read_task_state_projection;
9
+
10
+ pub(super) fn required_context(root: &Path) -> Vec<String> {
11
+ let mut paths = vec![
12
+ ".naome/task-state.json".to_string(),
13
+ ".naome/verification.json".to_string(),
14
+ "docs/naome/testing.md".to_string(),
15
+ ];
16
+ if root.join("docs/naome/context-economy.md").is_file() {
17
+ paths.push("docs/naome/context-economy.md".to_string());
18
+ }
19
+ paths
20
+ }
21
+
22
+ pub(super) fn read_task_state(root: &Path) -> Result<Value, NaomeError> {
23
+ Ok(read_task_state_projection(root)?.unwrap_or(Value::Null))
24
+ }
25
+
26
+ pub(super) fn active_allowed_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
27
+ let task_state = read_task_state(root)?;
28
+ Ok(task_state
29
+ .get("activeTask")
30
+ .and_then(|task| task.get("allowedPaths"))
31
+ .and_then(Value::as_array)
32
+ .into_iter()
33
+ .flatten()
34
+ .filter_map(Value::as_str)
35
+ .map(ToString::to_string)
36
+ .collect())
37
+ }
38
+
39
+ pub(super) fn changed_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
40
+ Ok(git::changed_paths(root)
41
+ .unwrap_or_default()
42
+ .into_iter()
43
+ .filter(|path| !path.trim().is_empty())
44
+ .collect())
45
+ }
46
+
47
+ pub(super) fn normalize_paths(paths: &[String]) -> Vec<String> {
48
+ paths
49
+ .iter()
50
+ .map(|path| normalize_path(path))
51
+ .collect::<BTreeSet<_>>()
52
+ .into_iter()
53
+ .collect()
54
+ }
55
+
56
+ pub(super) fn normalize_path(path: &str) -> String {
57
+ path.trim_start_matches("./").replace('\\', "/")
58
+ }
@@ -0,0 +1,47 @@
1
+ use std::path::Path;
2
+
3
+ use crate::models::NaomeError;
4
+ use crate::paths;
5
+ use crate::workflow::agent_types::EditSessionWatchdog;
6
+ use crate::workflow::types::WorkflowFinding;
7
+
8
+ use super::support::{active_allowed_paths, normalize_paths};
9
+
10
+ pub fn edit_session_watchdog(
11
+ root: &Path,
12
+ touched_paths: &[String],
13
+ ) -> Result<EditSessionWatchdog, NaomeError> {
14
+ let touched_paths = normalize_paths(touched_paths)
15
+ .into_iter()
16
+ .filter(|path| path != ".naome/task-state.json")
17
+ .collect::<Vec<_>>();
18
+ let allowed_paths = active_allowed_paths(root)?;
19
+ let out_of_scope = touched_paths
20
+ .iter()
21
+ .filter(|path| !allowed_paths.is_empty() && !paths::matches_any(path, &allowed_paths))
22
+ .cloned()
23
+ .collect::<Vec<_>>();
24
+ let mut next_commands = touched_paths
25
+ .iter()
26
+ .map(|path| format!("node .naome/bin/naome.js quality check --path {path}"))
27
+ .collect::<Vec<_>>();
28
+ next_commands.push("node .naome/bin/check-task-state.js --progress".to_string());
29
+
30
+ let findings = if out_of_scope.is_empty() {
31
+ Vec::new()
32
+ } else {
33
+ vec![WorkflowFinding::blocking(
34
+ "scope-drift",
35
+ "Touched paths are outside the active task scope.",
36
+ out_of_scope,
37
+ None,
38
+ )]
39
+ };
40
+
41
+ Ok(EditSessionWatchdog {
42
+ schema: "naome.edit-session-watchdog.v1".to_string(),
43
+ touched_paths,
44
+ next_commands,
45
+ findings,
46
+ })
47
+ }