@lamentis/naome 1.3.0 → 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 (137) 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/journal.rs +2 -7
  23. package/crates/naome-core/src/lib.rs +33 -10
  24. package/crates/naome-core/src/quality/adapter_ios.rs +131 -0
  25. package/crates/naome-core/src/quality/adapter_support.rs +67 -0
  26. package/crates/naome-core/src/quality/adapters.rs +81 -18
  27. package/crates/naome-core/src/quality/cache.rs +7 -9
  28. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +4 -7
  29. package/crates/naome-core/src/quality/config.rs +21 -3
  30. package/crates/naome-core/src/quality/mod.rs +138 -7
  31. package/crates/naome-core/src/quality/reconcile.rs +138 -0
  32. package/crates/naome-core/src/quality/reconcile_anchors.rs +64 -0
  33. package/crates/naome-core/src/quality/scanner/analysis.rs +20 -5
  34. package/crates/naome-core/src/quality/scanner.rs +62 -17
  35. package/crates/naome-core/src/quality/semantic/checks.rs +17 -0
  36. package/crates/naome-core/src/quality/semantic/route.rs +1 -1
  37. package/crates/naome-core/src/quality/structure/adapter_ios.rs +149 -0
  38. package/crates/naome-core/src/quality/structure/adapters.rs +60 -42
  39. package/crates/naome-core/src/quality/structure/checks/directory.rs +6 -4
  40. package/crates/naome-core/src/quality/structure/classify/roles.rs +51 -5
  41. package/crates/naome-core/src/quality/structure/config.rs +24 -3
  42. package/crates/naome-core/src/quality/structure/mod.rs +3 -0
  43. package/crates/naome-core/src/quality/types.rs +20 -1
  44. package/crates/naome-core/src/repository_model/detect.rs +188 -0
  45. package/crates/naome-core/src/repository_model/explain.rs +121 -0
  46. package/crates/naome-core/src/repository_model/path_scan.rs +67 -0
  47. package/crates/naome-core/src/repository_model/path_support.rs +59 -0
  48. package/crates/naome-core/src/repository_model/types.rs +152 -0
  49. package/crates/naome-core/src/repository_model/world.rs +48 -0
  50. package/crates/naome-core/src/repository_model/world_adapters.rs +145 -0
  51. package/crates/naome-core/src/repository_model/world_path_facts.rs +55 -0
  52. package/crates/naome-core/src/repository_model/world_paths.rs +168 -0
  53. package/crates/naome-core/src/repository_model.rs +164 -0
  54. package/crates/naome-core/src/route/builtin_checks.rs +40 -1
  55. package/crates/naome-core/src/task_ledger/import.rs +142 -0
  56. package/crates/naome-core/src/task_ledger/model.rs +13 -0
  57. package/crates/naome-core/src/task_ledger/proof_record.rs +52 -0
  58. package/crates/naome-core/src/task_ledger/read.rs +118 -0
  59. package/crates/naome-core/src/task_ledger/render.rs +55 -0
  60. package/crates/naome-core/src/task_ledger/write.rs +38 -0
  61. package/crates/naome-core/src/task_ledger.rs +48 -0
  62. package/crates/naome-core/src/task_state/api.rs +4 -2
  63. package/crates/naome-core/src/task_state/completed_refresh.rs +5 -16
  64. package/crates/naome-core/src/task_state/diff.rs +2 -2
  65. package/crates/naome-core/src/task_state/evidence.rs +8 -3
  66. package/crates/naome-core/src/task_state/mod.rs +1 -1
  67. package/crates/naome-core/src/task_state/progress.rs +13 -0
  68. package/crates/naome-core/src/task_state/proof_model.rs +8 -8
  69. package/crates/naome-core/src/task_state/repair.rs +2 -2
  70. package/crates/naome-core/src/task_state/task_diff_api.rs +9 -18
  71. package/crates/naome-core/src/task_state/types.rs +24 -0
  72. package/crates/naome-core/src/verification.rs +29 -18
  73. package/crates/naome-core/src/workflow/agent/capability.rs +194 -0
  74. package/crates/naome-core/src/workflow/agent/context_delta.rs +42 -0
  75. package/crates/naome-core/src/workflow/agent/decision.rs +32 -0
  76. package/crates/naome-core/src/workflow/agent/execution.rs +80 -0
  77. package/crates/naome-core/src/workflow/agent/proof.rs +24 -0
  78. package/crates/naome-core/src/workflow/agent/support.rs +58 -0
  79. package/crates/naome-core/src/workflow/agent/watchdog.rs +47 -0
  80. package/crates/naome-core/src/workflow/agent.rs +34 -0
  81. package/crates/naome-core/src/workflow/agent_types.rs +105 -0
  82. package/crates/naome-core/src/workflow/doctor.rs +39 -0
  83. package/crates/naome-core/src/workflow/mod.rs +11 -0
  84. package/crates/naome-core/src/workflow/output.rs +8 -2
  85. package/crates/naome-core/src/workflow/phase_inference.rs +1 -1
  86. package/crates/naome-core/tests/context.rs +99 -0
  87. package/crates/naome-core/tests/harness_health.rs +4 -0
  88. package/crates/naome-core/tests/install_plan.rs +12 -0
  89. package/crates/naome-core/tests/quality.rs +178 -2
  90. package/crates/naome-core/tests/quality_performance.rs +39 -2
  91. package/crates/naome-core/tests/quality_structure_adapters.rs +39 -0
  92. package/crates/naome-core/tests/repo_support/mod.rs +5 -1
  93. package/crates/naome-core/tests/repo_support/verification_values.rs +55 -0
  94. package/crates/naome-core/tests/repository_model.rs +281 -0
  95. package/crates/naome-core/tests/route_user_diff.rs +49 -1
  96. package/crates/naome-core/tests/semantic_legacy.rs +72 -38
  97. package/crates/naome-core/tests/task_ledger.rs +328 -0
  98. package/crates/naome-core/tests/task_state.rs +28 -0
  99. package/crates/naome-core/tests/verification.rs +29 -36
  100. package/crates/naome-core/tests/workflow_agent.rs +233 -0
  101. package/crates/naome-core/tests/workflow_agent_support/mod.rs +159 -0
  102. package/crates/naome-core/tests/workflow_doctor.rs +21 -0
  103. package/installer/codex-hooks.js +121 -0
  104. package/installer/context.js +10 -0
  105. package/installer/filesystem.js +4 -0
  106. package/installer/flows.js +8 -4
  107. package/installer/harness-files.js +6 -0
  108. package/installer/install-plan.js +4 -0
  109. package/installer/main.js +1 -1
  110. package/installer/native.js +1 -1
  111. package/native/darwin-arm64/naome +0 -0
  112. package/native/linux-x64/naome +0 -0
  113. package/package.json +1 -1
  114. package/templates/naome-root/.codex/config.toml +2 -0
  115. package/templates/naome-root/.codex/hooks.json +70 -0
  116. package/templates/naome-root/.naome/bin/check-harness-health.js +8 -6
  117. package/templates/naome-root/.naome/bin/check-task-state.js +12 -7
  118. package/templates/naome-root/.naome/bin/codex-hook-io.js +122 -0
  119. package/templates/naome-root/.naome/bin/codex-hook-policy.js +180 -0
  120. package/templates/naome-root/.naome/bin/codex-hook-runtime.js +174 -0
  121. package/templates/naome-root/.naome/bin/codex-hook.js +6 -0
  122. package/templates/naome-root/.naome/bin/naome.js +35 -4
  123. package/templates/naome-root/.naome/manifest.json +12 -6
  124. package/templates/naome-root/.naome/repository-model.json +6 -0
  125. package/templates/naome-root/.naome/repository-quality.json +3 -1
  126. package/templates/naome-root/.naome/verification.json +15 -1
  127. package/templates/naome-root/AGENTS.md +38 -83
  128. package/templates/naome-root/docs/naome/agent-workflow.md +54 -18
  129. package/templates/naome-root/docs/naome/codex-hooks.md +82 -0
  130. package/templates/naome-root/docs/naome/context-economy.md +73 -0
  131. package/templates/naome-root/docs/naome/first-run.md +25 -14
  132. package/templates/naome-root/docs/naome/index.md +18 -10
  133. package/templates/naome-root/docs/naome/repository-model.md +92 -0
  134. package/templates/naome-root/docs/naome/repository-quality.md +47 -7
  135. package/templates/naome-root/docs/naome/repository-structure.md +10 -3
  136. package/templates/naome-root/docs/naome/task-ledger.md +71 -0
  137. package/templates/naome-root/docs/naome/testing.md +16 -3
@@ -0,0 +1,34 @@
1
+ mod capability;
2
+ mod context_delta;
3
+ mod decision;
4
+ mod execution;
5
+ mod proof;
6
+ mod support;
7
+ mod watchdog;
8
+
9
+ use std::path::Path;
10
+
11
+ use crate::models::NaomeError;
12
+
13
+ use super::agent_types::AgentRunPlan;
14
+ use super::phases::CommandCheckResult;
15
+
16
+ pub use capability::repository_capability_graph;
17
+ pub use context_delta::context_delta_report;
18
+ pub use decision::decision_gate;
19
+ pub use execution::execution_plan;
20
+ pub use proof::proof_plan_for_task;
21
+ pub use watchdog::edit_session_watchdog;
22
+
23
+ pub fn agent_run_plan(root: &Path) -> Result<AgentRunPlan, NaomeError> {
24
+ let changed_paths = support::changed_paths(root)?;
25
+ Ok(AgentRunPlan {
26
+ schema: "naome.agent-run-plan.v1".to_string(),
27
+ execution_plan: execution_plan(root, &changed_paths)?,
28
+ context_delta: context_delta_report(root, &[])?,
29
+ proof_plan: proof_plan_for_task(root, &[] as &[CommandCheckResult])?,
30
+ capability_graph: repository_capability_graph(root)?,
31
+ edit_watchdog: edit_session_watchdog(root, &changed_paths)?,
32
+ decision_gate: decision_gate(root)?,
33
+ })
34
+ }
@@ -0,0 +1,105 @@
1
+ use serde::{Deserialize, Serialize};
2
+
3
+ use super::types::WorkflowFinding;
4
+
5
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6
+ #[serde(rename_all = "camelCase")]
7
+ pub struct AgentRunPlan {
8
+ pub schema: String,
9
+ pub execution_plan: ExecutionPlanLedger,
10
+ pub context_delta: ContextDeltaReport,
11
+ pub proof_plan: ProofPlan,
12
+ pub capability_graph: CapabilityGraph,
13
+ pub edit_watchdog: EditSessionWatchdog,
14
+ pub decision_gate: DecisionGate,
15
+ }
16
+
17
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18
+ #[serde(rename_all = "camelCase")]
19
+ pub struct ExecutionPlanLedger {
20
+ pub task_state: String,
21
+ pub changed_paths: Vec<String>,
22
+ pub steps: Vec<ExecutionPlanStep>,
23
+ }
24
+
25
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
26
+ #[serde(rename_all = "camelCase")]
27
+ pub struct ExecutionPlanStep {
28
+ pub id: String,
29
+ pub phase: String,
30
+ pub paths: Vec<String>,
31
+ pub commands: Vec<String>,
32
+ pub reason_codes: Vec<String>,
33
+ }
34
+
35
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36
+ #[serde(rename_all = "camelCase")]
37
+ pub struct ContextDeltaReport {
38
+ pub schema: String,
39
+ pub reusable_context: Vec<String>,
40
+ pub stale_context: Vec<String>,
41
+ pub unread_required_context: Vec<String>,
42
+ pub reason_codes: Vec<String>,
43
+ }
44
+
45
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46
+ #[serde(rename_all = "camelCase")]
47
+ pub struct ProofPlan {
48
+ pub schema: String,
49
+ pub recommended_check_ids: Vec<String>,
50
+ pub withheld_check_ids: Vec<String>,
51
+ pub evidence_paths: Vec<String>,
52
+ pub reason_codes: Vec<String>,
53
+ }
54
+
55
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56
+ #[serde(rename_all = "camelCase")]
57
+ pub struct CapabilityGraph {
58
+ pub schema: String,
59
+ pub capabilities: Vec<RepositoryCapability>,
60
+ pub roots: Vec<CapabilityRoot>,
61
+ pub verification_edges: Vec<VerificationEdge>,
62
+ }
63
+
64
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
65
+ #[serde(rename_all = "camelCase")]
66
+ pub struct RepositoryCapability {
67
+ pub id: String,
68
+ pub kind: String,
69
+ pub value: String,
70
+ pub evidence: Vec<String>,
71
+ }
72
+
73
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74
+ #[serde(rename_all = "camelCase")]
75
+ pub struct CapabilityRoot {
76
+ pub role: String,
77
+ pub path: String,
78
+ }
79
+
80
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
81
+ #[serde(rename_all = "camelCase")]
82
+ pub struct VerificationEdge {
83
+ pub check_id: String,
84
+ pub command: String,
85
+ pub evidence: Vec<String>,
86
+ }
87
+
88
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89
+ #[serde(rename_all = "camelCase")]
90
+ pub struct EditSessionWatchdog {
91
+ pub schema: String,
92
+ pub touched_paths: Vec<String>,
93
+ pub next_commands: Vec<String>,
94
+ pub findings: Vec<WorkflowFinding>,
95
+ }
96
+
97
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
98
+ #[serde(rename_all = "camelCase")]
99
+ pub struct DecisionGate {
100
+ pub schema: String,
101
+ pub action: String,
102
+ pub reason_codes: Vec<String>,
103
+ pub human_options: Vec<String>,
104
+ pub next_action: String,
105
+ }
@@ -5,6 +5,7 @@ use serde::Serialize;
5
5
 
6
6
  use crate::harness_health::{validate_harness_health, HarnessHealthOptions};
7
7
  use crate::models::NaomeError;
8
+ use crate::repository_model_drift;
8
9
  use crate::task_state::{validate_task_state, TaskStateMode, TaskStateOptions};
9
10
  use crate::verification_contract::validate_verification_contract;
10
11
 
@@ -18,6 +19,7 @@ pub struct DoctorReport {
18
19
  pub harness_health: DoctorSection,
19
20
  pub task_state: DoctorSection,
20
21
  pub verification: DoctorSection,
22
+ pub repository_model: RepositoryPolicySection,
21
23
  pub repository_quality: RepositoryPolicySection,
22
24
  pub repository_structure: RepositoryPolicySection,
23
25
  pub recommended_check_ids: Vec<String>,
@@ -38,6 +40,7 @@ pub struct RepositoryPolicySection {
38
40
  pub config_present: bool,
39
41
  pub baseline_present: bool,
40
42
  pub status: String,
43
+ pub messages: Vec<String>,
41
44
  }
42
45
 
43
46
  pub fn doctor_report(root: &Path) -> Result<DoctorReport, NaomeError> {
@@ -59,6 +62,7 @@ pub fn doctor_report(root: &Path) -> Result<DoctorReport, NaomeError> {
59
62
  let verification_errors = validate_verification_contract(root)?;
60
63
  let phase_plan = verification_phase_plan(root, &[])?;
61
64
 
65
+ let repository_model = repository_model_section(root)?;
62
66
  let repository_quality = policy_section(
63
67
  root,
64
68
  ".naome/repository-quality.json",
@@ -72,12 +76,14 @@ pub fn doctor_report(root: &Path) -> Result<DoctorReport, NaomeError> {
72
76
  let ok = harness_health.status == "ok"
73
77
  && task_state.status == "ok"
74
78
  && verification.status == "ok"
79
+ && repository_model.status == "ok"
75
80
  && repository_quality.status == "ok"
76
81
  && repository_structure.status == "ok";
77
82
  let next_action = next_action(
78
83
  &harness_health,
79
84
  &task_state,
80
85
  &verification,
86
+ &repository_model,
81
87
  &repository_quality,
82
88
  &repository_structure,
83
89
  );
@@ -88,6 +94,7 @@ pub fn doctor_report(root: &Path) -> Result<DoctorReport, NaomeError> {
88
94
  harness_health,
89
95
  task_state,
90
96
  verification,
97
+ repository_model,
91
98
  repository_quality,
92
99
  repository_structure,
93
100
  recommended_check_ids: phase_plan.recommended_check_ids,
@@ -117,13 +124,38 @@ fn policy_section(root: &Path, config_path: &str, baseline_path: &str) -> Reposi
117
124
  "missing"
118
125
  }
119
126
  .to_string(),
127
+ messages: Vec::new(),
120
128
  }
121
129
  }
122
130
 
131
+ fn repository_model_section(root: &Path) -> Result<RepositoryPolicySection, NaomeError> {
132
+ let drift = repository_model_drift(root)?;
133
+ let config_present = drift.model_present;
134
+ let status = if !config_present {
135
+ "missing"
136
+ } else if drift.stale {
137
+ "stale"
138
+ } else {
139
+ "ok"
140
+ };
141
+ let messages = if !config_present {
142
+ vec!["NAOME repository model is missing; run naome repo model --write.".to_string()]
143
+ } else {
144
+ drift.messages
145
+ };
146
+ Ok(RepositoryPolicySection {
147
+ config_present,
148
+ baseline_present: true,
149
+ status: status.to_string(),
150
+ messages,
151
+ })
152
+ }
153
+
123
154
  fn next_action(
124
155
  harness_health: &DoctorSection,
125
156
  task_state: &DoctorSection,
126
157
  verification: &DoctorSection,
158
+ repository_model: &RepositoryPolicySection,
127
159
  repository_quality: &RepositoryPolicySection,
128
160
  repository_structure: &RepositoryPolicySection,
129
161
  ) -> String {
@@ -136,6 +168,13 @@ fn next_action(
136
168
  if verification.status != "ok" {
137
169
  return "Fix .naome/verification.json before relying on gates.".to_string();
138
170
  }
171
+ if repository_model.status == "stale" {
172
+ return "Run naome repo model --write to refresh deterministic repository facts."
173
+ .to_string();
174
+ }
175
+ if repository_model.status != "ok" {
176
+ return "Run naome repo model --write to seed deterministic repository facts.".to_string();
177
+ }
139
178
  if repository_quality.status != "ok" || repository_structure.status != "ok" {
140
179
  return "Run naome quality init or naome sync to seed repository quality policy."
141
180
  .to_string();
@@ -1,3 +1,5 @@
1
+ mod agent;
2
+ mod agent_types;
1
3
  mod doctor;
2
4
  mod integrity;
3
5
  mod integrity_normalize;
@@ -10,6 +12,15 @@ mod policy;
10
12
  mod processes;
11
13
  mod types;
12
14
 
15
+ pub use agent::{
16
+ agent_run_plan, context_delta_report, decision_gate, edit_session_watchdog,
17
+ proof_plan_for_task, repository_capability_graph,
18
+ };
19
+ pub use agent_types::{
20
+ AgentRunPlan, CapabilityGraph, CapabilityRoot, ContextDeltaReport, DecisionGate,
21
+ EditSessionWatchdog, ExecutionPlanLedger, ExecutionPlanStep, ProofPlan, RepositoryCapability,
22
+ VerificationEdge,
23
+ };
13
24
  pub use doctor::{doctor_report, DoctorReport, DoctorSection, RepositoryPolicySection};
14
25
  pub use integrity::{refresh_integrity, IntegrityRefreshReport};
15
26
  pub use mutation::classify_mutations;
@@ -71,6 +71,8 @@ fn is_relevant_line(line: &str) -> bool {
71
71
  "outside allowedpaths",
72
72
  "denied",
73
73
  "missing",
74
+ ".log",
75
+ "artifact",
74
76
  ]
75
77
  .iter()
76
78
  .any(|needle| lower.contains(needle))
@@ -86,8 +88,12 @@ fn collect_paths(lines: &[String]) -> Vec<String> {
86
88
  '"' | '\'' | '`' | ':' | ',' | ';' | '(' | ')' | '[' | ']'
87
89
  )
88
90
  });
89
- if looks_like_path(cleaned) {
90
- paths.insert(cleaned.replace('\\', "/"));
91
+ let path_token = cleaned
92
+ .split_once(':')
93
+ .map(|(path, _)| path)
94
+ .unwrap_or(cleaned);
95
+ if looks_like_path(path_token) {
96
+ paths.insert(path_token.replace('\\', "/"));
91
97
  }
92
98
  }
93
99
  }
@@ -54,7 +54,7 @@ fn inferred_phase<'a>(check: &'a CheckDefinition, release_gate_ids: &HashSet<&st
54
54
  if id == "diff-check" || command.contains("diff --check") {
55
55
  return "diff-check";
56
56
  }
57
- if id.contains("quality") {
57
+ if id.contains("quality") || id.contains("semantic") {
58
58
  return "quality";
59
59
  }
60
60
  if id.contains("health") || id.contains("task-state") || command.contains("check-task-state") {
@@ -0,0 +1,99 @@
1
+ mod repo_support;
2
+
3
+ use naome_core::{select_context_for_changed_paths, select_context_for_prompt};
4
+
5
+ use repo_support::{verification_value, TestRepo};
6
+
7
+ #[test]
8
+ fn changed_context_selects_touched_files_and_compact_quality_capsule() {
9
+ let repo = context_repo("context-changed-quality");
10
+ repo.write_file("packages/tool/src/quality/scanner.rs", "pub fn scan() {}\n");
11
+ repo.write_file("docs/naome/testing.md", "# Testing\n");
12
+ repo.commit_all("baseline");
13
+ repo.write_file(
14
+ "packages/tool/src/quality/scanner.rs",
15
+ "pub fn scan() {}\npub fn changed() {}\n",
16
+ );
17
+ repo.write_file("docs/naome/testing.md", "# Testing\n\nchanged\n");
18
+
19
+ let selection = select_context_for_changed_paths(repo.path()).unwrap();
20
+
21
+ assert_eq!(selection.schema, "naome.context-selection.v1");
22
+ assert_eq!(selection.mode, "changed");
23
+ assert_eq!(selection.capsule.id, "quality-work");
24
+ assert!(selection.required_context.iter().any(|item| {
25
+ item.path == "packages/tool/src/quality/scanner.rs" && item.reason == "changed_path"
26
+ }));
27
+ assert!(selection.commands.iter().any(|command| {
28
+ command == "node .naome/bin/naome.js quality check --path packages/tool/src/quality/scanner.rs"
29
+ }));
30
+ assert!(selection
31
+ .commands
32
+ .contains(&"node .naome/bin/naome.js quality check --changed".to_string()));
33
+ assert!(selection
34
+ .forbidden_context
35
+ .contains(&".naome/archive/**".to_string()));
36
+ assert!(selection.budget_ledger.selected_files < 6);
37
+ assert!(selection.budget_ledger.estimated_tokens > 0);
38
+ }
39
+
40
+ #[test]
41
+ fn prompt_context_uses_file_mentions_without_broad_markdown_context() {
42
+ let repo = context_repo("context-prompt-paths");
43
+ repo.write_file("packages/app/src/lib.rs", "pub fn app() {}\n");
44
+ repo.write_file("docs/naome/repository-quality.md", "# Quality\n");
45
+ repo.commit_all("baseline");
46
+
47
+ let selection = select_context_for_prompt(
48
+ repo.path(),
49
+ "Please update packages/app/src/lib.rs and run a path-scoped quality check.",
50
+ )
51
+ .unwrap();
52
+
53
+ assert_eq!(selection.mode, "prompt");
54
+ assert_eq!(selection.capsule.id, "source-work");
55
+ assert_eq!(
56
+ selection.required_context[0].path,
57
+ "packages/app/src/lib.rs"
58
+ );
59
+ assert!(!selection
60
+ .required_context
61
+ .iter()
62
+ .any(|item| item.path == "docs/naome/repository-quality.md"));
63
+ assert!(selection
64
+ .optional_context
65
+ .iter()
66
+ .any(|item| item.path == "docs/naome/repository-quality.md"));
67
+ }
68
+
69
+ #[test]
70
+ fn context_selection_reports_over_budget_when_many_paths_change() {
71
+ let repo = context_repo("context-budget");
72
+ for index in 0..20 {
73
+ repo.write_file(&format!("src/file_{index}.rs"), "pub fn before() {}\n");
74
+ }
75
+ repo.commit_all("baseline");
76
+ for index in 0..20 {
77
+ repo.write_file(&format!("src/file_{index}.rs"), "pub fn after() {}\n");
78
+ }
79
+
80
+ let selection = select_context_for_changed_paths(repo.path()).unwrap();
81
+
82
+ assert_eq!(selection.budget_ledger.selected_files, 12);
83
+ assert!(selection.budget_ledger.avoided_files >= 8);
84
+ assert!(selection
85
+ .budget_ledger
86
+ .reason_codes
87
+ .contains(&"context_file_budget_reached".to_string()));
88
+ }
89
+
90
+ fn context_repo(name: &str) -> TestRepo {
91
+ let repo = TestRepo::new(name);
92
+ repo.init_git();
93
+ repo.write_file(".naomeignore", ".naome/archive/\n.naome/cache/\n");
94
+ repo.write_naome_json(
95
+ "verification.json",
96
+ verification_value("ready", vec![], vec![]),
97
+ );
98
+ repo
99
+ }
@@ -18,7 +18,9 @@ const MACHINE_OWNED_PATHS: &[&str] = &[
18
18
  "docs/naome/index.md",
19
19
  "docs/naome/first-run.md",
20
20
  "docs/naome/agent-workflow.md",
21
+ "docs/naome/context-economy.md",
21
22
  "docs/naome/execution.md",
23
+ "docs/naome/task-ledger.md",
22
24
  "docs/naome/upgrade.md",
23
25
  ];
24
26
 
@@ -29,12 +31,14 @@ const PROJECT_OWNED_PATHS: &[&str] = &[
29
31
  ".naome/task-state.json",
30
32
  ".naome/upgrade-state.json",
31
33
  ".naome/verification.json",
34
+ ".naome/repository-model.json",
32
35
  ".naome/repository-quality.json",
33
36
  ".naome/repository-structure.json",
34
37
  ".naome/repository-quality-baseline.json",
35
38
  "docs/naome/architecture.md",
36
39
  "docs/naome/decisions.md",
37
40
  "docs/naome/repo-profile.md",
41
+ "docs/naome/repository-model.md",
38
42
  "docs/naome/repository-quality.md",
39
43
  "docs/naome/repository-structure.md",
40
44
  "docs/naome/security.md",
@@ -8,6 +8,7 @@ fn install_plan_marks_machine_docs_and_bins_local_only() {
8
8
  assert_eq!(plan.harness_version, "1.0.0");
9
9
  assert!(plan.machine_owned.contains(&"docs/naome/execution.md"));
10
10
  assert!(plan.project_owned.contains(&"docs/naome/architecture.md"));
11
+ assert!(!plan.project_owned.contains(&".naome/tasks/**"));
11
12
  assert!(plan
12
13
  .local_only_machine_owned
13
14
  .contains(&".naome/bin/check-task-state.js"));
@@ -18,6 +19,17 @@ fn install_plan_marks_machine_docs_and_bins_local_only() {
18
19
  assert!(!plan
19
20
  .local_only_machine_owned
20
21
  .contains(&"docs/naome/architecture.md"));
22
+ assert!(plan
23
+ .optional_codex_hook_paths
24
+ .contains(&".codex/hooks.json"));
25
+ assert!(plan
26
+ .optional_codex_hook_paths
27
+ .contains(&".codex/config.toml"));
28
+ assert!(plan
29
+ .optional_codex_hook_paths
30
+ .contains(&".naome/bin/codex-hook.js"));
31
+ assert!(!plan.machine_owned.contains(&".naome/bin/codex-hook.js"));
32
+ assert!(!plan.project_owned.contains(&".codex/hooks.json"));
21
33
  }
22
34
 
23
35
  #[test]
@@ -2,8 +2,8 @@ use std::fs;
2
2
 
3
3
  use naome_core::{
4
4
  check_repository_quality, init_repository_quality, init_repository_quality_with_mode,
5
- plan_quality_cleanup, route_quality_cleanup, seed_builtin_verification_checks, QualityInitMode,
6
- QualityMode,
5
+ plan_quality_cleanup, reconcile_repository_quality, route_quality_cleanup,
6
+ seed_builtin_verification_checks, QualityInitMode, QualityMode,
7
7
  };
8
8
  use serde_json::Value;
9
9
 
@@ -248,6 +248,174 @@ fn init_generates_adapter_selection_without_product_specific_path_rules() {
248
248
  assert!(!serialized_config.contains("naome-template-harness"));
249
249
  }
250
250
 
251
+ #[test]
252
+ fn init_auto_detects_ios_swift_adapter_set() {
253
+ let repo = QualityFixture::new("quality-ios-swift-adapters");
254
+ repo.write(
255
+ "Package.swift",
256
+ "// swift-tools-version: 5.9\nimport PackageDescription\n",
257
+ );
258
+ repo.write(
259
+ "Demo.xcodeproj/project.pbxproj",
260
+ "// !$*UTF8*$!\n{ archiveVersion = 1; }\n",
261
+ );
262
+ repo.write(
263
+ "Sources/App/App.swift",
264
+ "import SwiftUI\n@main struct DemoApp: App { var body: some Scene { WindowGroup { ContentView() } } }\n",
265
+ );
266
+ repo.write(
267
+ "Tests/AppTests/AppTests.swift",
268
+ "import XCTest\nfinal class AppTests: XCTestCase {}\n",
269
+ );
270
+ repo.write(
271
+ "Resources/Assets.xcassets/AppIcon.appiconset/Contents.json",
272
+ "{}\n",
273
+ );
274
+ repo.write("Generated/R.generated.swift", "enum R {}\n");
275
+ repo.commit_all("ios swift project");
276
+
277
+ init_repository_quality(repo.path()).unwrap();
278
+ let quality: Value = serde_json::from_str(
279
+ &fs::read_to_string(repo.path().join(".naome/repository-quality.json")).unwrap(),
280
+ )
281
+ .unwrap();
282
+ let structure: Value = serde_json::from_str(
283
+ &fs::read_to_string(repo.path().join(".naome/repository-structure.json")).unwrap(),
284
+ )
285
+ .unwrap();
286
+
287
+ for adapter in [
288
+ "swift",
289
+ "xcode",
290
+ "xctest",
291
+ "swiftui",
292
+ "ios-app-structure",
293
+ "swift-package",
294
+ "ios-resources",
295
+ "generated-ios",
296
+ ] {
297
+ assert!(
298
+ has_adapter(&quality, adapter),
299
+ "missing quality adapter {adapter}: {quality:#?}"
300
+ );
301
+ assert!(
302
+ has_adapter(&structure, adapter),
303
+ "missing structure adapter {adapter}: {structure:#?}"
304
+ );
305
+ }
306
+ }
307
+
308
+ #[test]
309
+ fn changed_check_reports_stale_adapter_policy_after_stack_changes() {
310
+ let repo = QualityFixture::new("quality-stale-adapter-policy");
311
+ repo.write_quality_config_with_adapters(450, 100, 10, &[]);
312
+ repo.write_structure_config(serde_json::json!({
313
+ "enabledAdapters": []
314
+ }));
315
+ repo.commit_all("initial quality policy");
316
+ repo.write(
317
+ "Package.swift",
318
+ "// swift-tools-version: 5.9\nimport PackageDescription\n",
319
+ );
320
+ repo.write(
321
+ "Sources/App/App.swift",
322
+ "public struct DemoApp { public init() {} }\n",
323
+ );
324
+
325
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
326
+
327
+ assert!(!report.ok);
328
+ assert!(report.violations.iter().any(|violation| {
329
+ violation.check_id == "adapter-policy-stale"
330
+ && violation.path == ".naome/repository-quality.json"
331
+ && violation.message.contains("swift")
332
+ && violation
333
+ .related_paths
334
+ .contains(&".naome/repository-structure.json".to_string())
335
+ }));
336
+ }
337
+
338
+ #[test]
339
+ fn changed_check_reports_stale_repository_model_after_stack_changes() {
340
+ let repo = QualityFixture::new("quality-repository-model-stale");
341
+ repo.write_quality_config();
342
+ repo.write_structure_config(serde_json::json!({}));
343
+ repo.write(
344
+ ".naome/repository-model.json",
345
+ "{\"schema\":\"naome.repository-model.v1\",\"version\":1,\"status\":\"ready\",\"facts\":[]}\n",
346
+ );
347
+ repo.write("README.md", "# Baseline\n");
348
+ repo.commit_all("baseline");
349
+
350
+ repo.write("Package.swift", "// swift-tools-version: 5.9\n");
351
+
352
+ let report = check_repository_quality(repo.path(), QualityMode::Changed).unwrap();
353
+
354
+ assert!(!report.ok);
355
+ assert!(report
356
+ .summary
357
+ .reason_codes
358
+ .contains(&"repository_model_stale".to_string()));
359
+ assert!(report.violations.iter().any(|violation| {
360
+ violation.check_id == "repository-model-stale"
361
+ && violation.path == ".naome/repository-model.json"
362
+ && violation
363
+ .related_paths
364
+ .contains(&"Package.swift".to_string())
365
+ }));
366
+ }
367
+
368
+ #[test]
369
+ fn reconcile_write_updates_stale_quality_and_structure_adapter_policy() {
370
+ let repo = QualityFixture::new("quality-reconcile-adapters");
371
+ repo.write_quality_config_with_adapters(450, 100, 10, &[]);
372
+ repo.write_structure_config(serde_json::json!({
373
+ "enabledAdapters": []
374
+ }));
375
+ repo.write(
376
+ "Package.swift",
377
+ "// swift-tools-version: 5.9\nimport PackageDescription\n",
378
+ );
379
+ repo.write(
380
+ "Sources/App/App.swift",
381
+ "public struct DemoApp { public init() {} }\n",
382
+ );
383
+
384
+ let planned = reconcile_repository_quality(repo.path(), false).unwrap();
385
+ assert!(!planned.ok);
386
+ assert!(planned.stale);
387
+ assert!(planned
388
+ .missing_quality_adapters
389
+ .contains(&"swift".to_string()));
390
+ assert!(planned.updated_paths.is_empty());
391
+
392
+ let written = reconcile_repository_quality(repo.path(), true).unwrap();
393
+ assert!(written.ok);
394
+ assert!(!written.stale);
395
+ assert_eq!(
396
+ written.updated_paths,
397
+ vec![
398
+ ".naome/repository-quality.json".to_string(),
399
+ ".naome/repository-structure.json".to_string()
400
+ ]
401
+ );
402
+
403
+ let quality: Value = serde_json::from_str(
404
+ &fs::read_to_string(repo.path().join(".naome/repository-quality.json")).unwrap(),
405
+ )
406
+ .unwrap();
407
+ let structure: Value = serde_json::from_str(
408
+ &fs::read_to_string(repo.path().join(".naome/repository-structure.json")).unwrap(),
409
+ )
410
+ .unwrap();
411
+ assert!(has_adapter(&quality, "swift"));
412
+ assert!(has_adapter(&structure, "swift"));
413
+
414
+ let repeated = reconcile_repository_quality(repo.path(), true).unwrap();
415
+ assert!(repeated.ok);
416
+ assert!(repeated.updated_paths.is_empty());
417
+ }
418
+
251
419
  #[test]
252
420
  fn enabled_adapters_apply_their_rules_without_materialized_path_rules() {
253
421
  let repo = QualityFixture::new("quality-adapter-runtime-config");
@@ -266,6 +434,14 @@ fn enabled_adapters_apply_their_rules_without_materialized_path_rules() {
266
434
  }));
267
435
  }
268
436
 
437
+ fn has_adapter(config: &Value, adapter: &str) -> bool {
438
+ config["enabledAdapters"]
439
+ .as_array()
440
+ .unwrap()
441
+ .iter()
442
+ .any(|value| value.as_str() == Some(adapter))
443
+ }
444
+
269
445
  #[test]
270
446
  fn seeding_builtin_verification_wires_repository_quality_into_change_types() {
271
447
  let repo = QualityFixture::new("quality-verification-wiring");