@lamentis/naome 1.3.5 → 1.3.7

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 (36) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +4 -3
  3. package/crates/naome-cli/Cargo.toml +1 -1
  4. package/crates/naome-core/Cargo.toml +1 -1
  5. package/crates/naome-core/src/git.rs +1 -26
  6. package/crates/naome-core/src/information_architecture.rs +15 -26
  7. package/crates/naome-core/src/install_plan.rs +2 -0
  8. package/crates/naome-core/src/lib.rs +1 -0
  9. package/crates/naome-core/src/paths.rs +27 -0
  10. package/crates/naome-core/src/quality/scanner/repo_paths.rs +2 -47
  11. package/crates/naome-core/src/repo_paths.rs +76 -0
  12. package/crates/naome-core/src/repository_model/path_scan.rs +2 -23
  13. package/crates/naome-core/src/task_ledger/write.rs +6 -0
  14. package/crates/naome-core/src/task_ledger.rs +50 -2
  15. package/crates/naome-core/src/task_state/util.rs +21 -17
  16. package/crates/naome-core/tests/decision.rs +2 -10
  17. package/crates/naome-core/tests/information_architecture.rs +7 -10
  18. package/crates/naome-core/tests/install_plan.rs +2 -0
  19. package/crates/naome-core/tests/quality_structure_support/mod.rs +6 -25
  20. package/crates/naome-core/tests/repo_support/mod.rs +3 -5
  21. package/crates/naome-core/tests/repo_support/repo_helpers.rs +2 -9
  22. package/crates/naome-core/tests/repo_support/verification.rs +1 -8
  23. package/crates/naome-core/tests/repo_support/verification_values.rs +20 -18
  24. package/crates/naome-core/tests/task_ledger.rs +69 -1
  25. package/crates/naome-core/tests/task_state_compact_support/states.rs +10 -8
  26. package/crates/naome-core/tests/task_state_support/states.rs +8 -8
  27. package/crates/naome-core/tests/workflow_support/mod.rs +2 -0
  28. package/installer/flows.js +55 -6
  29. package/native/darwin-arm64/naome +0 -0
  30. package/native/linux-x64/naome +0 -0
  31. package/package.json +1 -2
  32. package/templates/naome-root/.naome/manifest.json +2 -2
  33. package/templates/naome-root/.naomeignore +1 -0
  34. package/templates/naome-root/docs/naome/agent-workflow.md +8 -10
  35. package/templates/naome-root/docs/naome/architecture.md +8 -8
  36. package/templates/naome-root/docs/naome/task-ledger.md +20 -17
package/Cargo.lock CHANGED
@@ -76,7 +76,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
76
76
 
77
77
  [[package]]
78
78
  name = "naome-cli"
79
- version = "1.3.5"
79
+ version = "1.3.7"
80
80
  dependencies = [
81
81
  "naome-core",
82
82
  "serde_json",
@@ -84,7 +84,7 @@ dependencies = [
84
84
 
85
85
  [[package]]
86
86
  name = "naome-core"
87
- version = "1.3.5"
87
+ version = "1.3.7"
88
88
  dependencies = [
89
89
  "serde",
90
90
  "serde_json",
package/README.md CHANGED
@@ -84,8 +84,9 @@ naome commit -m "type(scope): summary"
84
84
 
85
85
  `naome sync` installs or repairs the local harness files. It does not run a
86
86
  hidden full-repository quality scan. It also migrates any active legacy
87
- task-state into the task ledger automatically. If quality policy is newly
88
- seeded, run `naome quality init --baseline` deliberately; use `--deep` or
87
+ task-state into the local task ledger automatically and untracks local
88
+ `.naome/tasks/` runtime folders from Git. If quality policy is newly seeded,
89
+ run `naome quality init --baseline` deliberately; use `--deep` or
89
90
  `--deep-baseline` only when you want expensive repository-wide checks.
90
91
 
91
92
  ## Repository Docs
@@ -111,7 +112,7 @@ The main local policy files are:
111
112
  quality policy.
112
113
  - `.naome/repository-structure.json` for path role, module, and directory
113
114
  structure policy.
114
- - `.naome/task-state.json` for active task state and proof.
115
+ - `.naome/task-state.json` for the compact committed active task projection.
115
116
 
116
117
  Product defaults stay generic. Repository-specific policy belongs in the local
117
118
  `.naome/` config files.
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-cli"
3
- version = "1.3.5"
3
+ version = "1.3.7"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-core"
3
- version = "1.3.5"
3
+ version = "1.3.7"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -1,4 +1,3 @@
1
- use std::fs;
2
1
  use std::path::Path;
3
2
  use std::process::Command;
4
3
 
@@ -6,7 +5,7 @@ use crate::models::NaomeError;
6
5
  use crate::paths;
7
6
 
8
7
  pub fn changed_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
9
- let ignored_patterns = read_naomeignore_patterns(root);
8
+ let ignored_patterns = paths::naomeignore_patterns(root);
10
9
  let output = Command::new("git")
11
10
  .args(["status", "--porcelain=v1", "-z", "-uall"])
12
11
  .current_dir(root)
@@ -57,27 +56,3 @@ pub fn changed_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
57
56
  paths.dedup();
58
57
  Ok(paths)
59
58
  }
60
-
61
- fn read_naomeignore_patterns(root: &Path) -> Vec<String> {
62
- let Ok(content) = fs::read_to_string(root.join(".naomeignore")) else {
63
- return Vec::new();
64
- };
65
-
66
- let mut patterns = content
67
- .lines()
68
- .map(str::trim)
69
- .filter(|line| !line.is_empty() && !line.starts_with('#') && !line.starts_with('!'))
70
- .map(normalize_ignore_pattern)
71
- .collect::<Vec<_>>();
72
- patterns.push(".naome/cache/**".to_string());
73
- patterns
74
- }
75
-
76
- fn normalize_ignore_pattern(pattern: &str) -> String {
77
- let normalized = pattern.trim_start_matches("./").replace('\\', "/");
78
- if normalized.ends_with('/') {
79
- format!("{normalized}**")
80
- } else {
81
- normalized
82
- }
83
- }
@@ -36,12 +36,6 @@ const CLASSES: &[(&str, &str, &str, &str)] = &[
36
36
  "commit",
37
37
  "restore_from_repository_or_package",
38
38
  ),
39
- (
40
- "durable_task_ledger",
41
- "Committed task intent, scope, and lifecycle records.",
42
- "commit",
43
- "restore_from_repository",
44
- ),
45
39
  (
46
40
  "generated_projection",
47
41
  "Compatibility views generated from durable state.",
@@ -67,23 +61,18 @@ const CLASSES: &[(&str, &str, &str, &str)] = &[
67
61
  "restore_from_repository",
68
62
  ),
69
63
  ];
70
- struct RuleSeed {
71
- path_pattern: &'static str,
72
- class: &'static str,
73
- }
74
-
75
- const RULES: &[RuleSeed] = &[
76
- RuleSeed { path_pattern: ".naome/tmp/**", class: "local_runtime_state" },
77
- RuleSeed { path_pattern: ".naome/task-state.json", class: "generated_projection" },
78
- RuleSeed { path_pattern: ".naome/tasks/active.json", class: "durable_task_ledger" },
79
- RuleSeed { path_pattern: ".naome/tasks/*/task.json", class: "durable_task_ledger" },
80
- RuleSeed { path_pattern: ".naome/tasks/*/events.jsonl", class: "durable_task_ledger" },
81
- RuleSeed { path_pattern: ".naome/tasks/*/proofs/*.json", class: "run_evidence" },
82
- RuleSeed { path_pattern: ".naome/manifest.json", class: "durable_project_state" },
83
- RuleSeed { path_pattern: ".naome/verification.json", class: "durable_project_state" },
84
- RuleSeed { path_pattern: ".naome/repository-quality*.json", class: "durable_project_state" },
85
- RuleSeed { path_pattern: "docs/naome/**", class: "durable_project_state" },
86
- RuleSeed { path_pattern: "packages/naome/templates/naome-root/**", class: "durable_project_state" },
64
+ const RULES: &[(&str, &str)] = &[
65
+ (".naome/tmp/**", "local_runtime_state"),
66
+ (".naome/task-state.json", "generated_projection"),
67
+ (".naome/tasks/**", "local_runtime_state"),
68
+ (".naome/manifest.json", "durable_project_state"),
69
+ (".naome/verification.json", "durable_project_state"),
70
+ (".naome/repository-quality*.json", "durable_project_state"),
71
+ ("docs/naome/**", "durable_project_state"),
72
+ (
73
+ "packages/naome/templates/naome-root/**",
74
+ "durable_project_state",
75
+ ),
87
76
  ];
88
77
  pub fn information_architecture_report() -> InformationArchitectureReport {
89
78
  InformationArchitectureReport {
@@ -95,7 +84,7 @@ pub fn information_architecture_report() -> InformationArchitectureReport {
95
84
  .collect(),
96
85
  rules: RULES
97
86
  .iter()
98
- .map(|rule| rule_from_class(rule.path_pattern, rule.class))
87
+ .map(|(path_pattern, class)| rule_from_class(path_pattern, class))
99
88
  .collect(),
100
89
  }
101
90
  }
@@ -104,8 +93,8 @@ pub fn classify_information_path(path: &str) -> InformationPathClassification {
104
93
  let path = normalize_path(path);
105
94
  let class_id = RULES
106
95
  .iter()
107
- .find(|rule| matches_information_rule(&path, rule.path_pattern))
108
- .map(|rule| rule.class)
96
+ .find(|(pattern, _)| matches_information_rule(&path, pattern))
97
+ .map(|(_, class)| *class)
109
98
  .unwrap_or("product_source");
110
99
  let class = class_by_id(class_id);
111
100
  InformationPathClassification {
@@ -81,11 +81,13 @@ pub fn install_plan(harness_version: impl Into<String>) -> InstallPlan {
81
81
  ".naome/bin/naome-rust*",
82
82
  ".naome/cache/",
83
83
  ".naome/task-journal.jsonl",
84
+ ".naome/tasks/",
84
85
  ];
85
86
  git_exclude_entries.extend_from_slice(LOCAL_ONLY_MACHINE_OWNED_PATHS);
86
87
 
87
88
  let mut git_untrack_paths = LOCAL_ONLY_MACHINE_OWNED_PATHS.to_vec();
88
89
  git_untrack_paths.extend_from_slice(LOCAL_NATIVE_BINARY_PATHS);
90
+ git_untrack_paths.push(".naome/tasks");
89
91
 
90
92
  InstallPlan {
91
93
  schema: "naome.install-plan.v1",
@@ -9,6 +9,7 @@ mod journal;
9
9
  mod models;
10
10
  mod paths;
11
11
  mod quality;
12
+ mod repo_paths;
12
13
  mod repository_model;
13
14
  mod route;
14
15
  mod task_ledger;
@@ -1,9 +1,36 @@
1
+ use std::fs;
2
+ use std::path::Path;
3
+
4
+ pub fn naomeignore_patterns(root: &Path) -> Vec<String> {
5
+ let Ok(content) = fs::read_to_string(root.join(".naomeignore")) else {
6
+ return Vec::new();
7
+ };
8
+
9
+ let mut patterns = content
10
+ .lines()
11
+ .map(str::trim)
12
+ .filter(|line| !line.is_empty() && !line.starts_with('#') && !line.starts_with('!'))
13
+ .map(normalize_ignore_pattern)
14
+ .collect::<Vec<_>>();
15
+ patterns.push(".naome/cache/**".to_string());
16
+ patterns
17
+ }
18
+
1
19
  pub fn matches_any(path: &str, patterns: &[String]) -> bool {
2
20
  patterns
3
21
  .iter()
4
22
  .any(|pattern| matches_pattern(path, pattern))
5
23
  }
6
24
 
25
+ fn normalize_ignore_pattern(pattern: &str) -> String {
26
+ let normalized = pattern.trim_start_matches("./").replace('\\', "/");
27
+ if normalized.ends_with('/') {
28
+ format!("{normalized}**")
29
+ } else {
30
+ normalized
31
+ }
32
+ }
33
+
7
34
  fn matches_pattern(path: &str, pattern: &str) -> bool {
8
35
  let normalized_path = path.replace('\\', "/");
9
36
  let normalized_pattern = pattern.replace('\\', "/");
@@ -4,36 +4,10 @@ use std::path::Path;
4
4
  use std::process::Command;
5
5
 
6
6
  use crate::models::NaomeError;
7
+ use crate::repo_paths::{collect_tracked_and_untracked_paths, RepoPathFallback};
7
8
 
8
9
  pub(crate) fn collect_repo_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
9
- let output = Command::new("git")
10
- .args([
11
- "ls-files",
12
- "-z",
13
- "--cached",
14
- "--others",
15
- "--exclude-standard",
16
- ])
17
- .current_dir(root)
18
- .output();
19
- if let Ok(output) = output {
20
- if output.status.success() {
21
- let mut paths = output
22
- .stdout
23
- .split(|byte| *byte == 0)
24
- .filter(|entry| !entry.is_empty())
25
- .map(|entry| String::from_utf8_lossy(entry).replace('\\', "/"))
26
- .collect::<Vec<_>>();
27
- paths.sort();
28
- paths.dedup();
29
- return Ok(paths);
30
- }
31
- }
32
-
33
- let mut paths = Vec::new();
34
- collect_files_recursive(root, root, &mut paths)?;
35
- paths.sort();
36
- Ok(paths)
10
+ collect_tracked_and_untracked_paths(root, RepoPathFallback::Filesystem)
37
11
  }
38
12
 
39
13
  pub(super) fn added_lines_by_path(
@@ -103,25 +77,6 @@ pub(super) fn tracked_blob_hashes(root: &Path) -> Result<HashMap<String, String>
103
77
  Ok(hashes)
104
78
  }
105
79
 
106
- fn collect_files_recursive(
107
- root: &Path,
108
- dir: &Path,
109
- paths: &mut Vec<String>,
110
- ) -> Result<(), NaomeError> {
111
- for entry in fs::read_dir(dir)? {
112
- let entry = entry?;
113
- let path = entry.path();
114
- if path.is_dir() {
115
- collect_files_recursive(root, &path, paths)?;
116
- } else if path.is_file() {
117
- if let Ok(relative) = path.strip_prefix(root) {
118
- paths.push(relative.to_string_lossy().replace('\\', "/"));
119
- }
120
- }
121
- }
122
- Ok(())
123
- }
124
-
125
80
  fn untracked_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
126
81
  let output = Command::new("git")
127
82
  .args(["ls-files", "--others", "--exclude-standard", "-z"])
@@ -0,0 +1,76 @@
1
+ use std::fs;
2
+ use std::path::Path;
3
+ use std::process::Command;
4
+
5
+ use crate::models::NaomeError;
6
+
7
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
8
+ pub(crate) enum RepoPathFallback {
9
+ Empty,
10
+ Filesystem,
11
+ }
12
+
13
+ pub(crate) fn collect_tracked_and_untracked_paths(
14
+ root: &Path,
15
+ fallback: RepoPathFallback,
16
+ ) -> Result<Vec<String>, NaomeError> {
17
+ let output = Command::new("git")
18
+ .args([
19
+ "ls-files",
20
+ "-z",
21
+ "--cached",
22
+ "--others",
23
+ "--exclude-standard",
24
+ ])
25
+ .current_dir(root)
26
+ .output();
27
+
28
+ if let Ok(output) = output {
29
+ if output.status.success() {
30
+ return Ok(sorted_unique_paths(
31
+ output
32
+ .stdout
33
+ .split(|byte| *byte == 0)
34
+ .filter(|entry| !entry.is_empty())
35
+ .map(|entry| String::from_utf8_lossy(entry).replace('\\', "/"))
36
+ .collect(),
37
+ ));
38
+ }
39
+ }
40
+
41
+ match fallback {
42
+ RepoPathFallback::Empty => Ok(Vec::new()),
43
+ RepoPathFallback::Filesystem => collect_filesystem_paths(root),
44
+ }
45
+ }
46
+
47
+ fn collect_filesystem_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
48
+ let mut paths = Vec::new();
49
+ collect_files_recursive(root, root, &mut paths)?;
50
+ Ok(sorted_unique_paths(paths))
51
+ }
52
+
53
+ fn collect_files_recursive(
54
+ root: &Path,
55
+ dir: &Path,
56
+ paths: &mut Vec<String>,
57
+ ) -> Result<(), NaomeError> {
58
+ for entry in fs::read_dir(dir)? {
59
+ let entry = entry?;
60
+ let path = entry.path();
61
+ if path.is_dir() {
62
+ collect_files_recursive(root, &path, paths)?;
63
+ } else if path.is_file() {
64
+ if let Ok(relative) = path.strip_prefix(root) {
65
+ paths.push(relative.to_string_lossy().replace('\\', "/"));
66
+ }
67
+ }
68
+ }
69
+ Ok(())
70
+ }
71
+
72
+ fn sorted_unique_paths(mut paths: Vec<String>) -> Vec<String> {
73
+ paths.sort();
74
+ paths.dedup();
75
+ paths
76
+ }
@@ -1,32 +1,11 @@
1
1
  use std::collections::BTreeSet;
2
2
  use std::path::Path;
3
- use std::process::Command;
4
3
 
5
4
  use crate::models::NaomeError;
5
+ use crate::repo_paths::{collect_tracked_and_untracked_paths, RepoPathFallback};
6
6
 
7
7
  pub(super) fn collect_repo_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
8
- let output = Command::new("git")
9
- .args([
10
- "ls-files",
11
- "-z",
12
- "--cached",
13
- "--others",
14
- "--exclude-standard",
15
- ])
16
- .current_dir(root)
17
- .output()?;
18
- if !output.status.success() {
19
- return Ok(Vec::new());
20
- }
21
- let mut paths = output
22
- .stdout
23
- .split(|byte| *byte == 0)
24
- .filter(|entry| !entry.is_empty())
25
- .map(|entry| String::from_utf8_lossy(entry).replace('\\', "/"))
26
- .collect::<Vec<_>>();
27
- paths.sort();
28
- paths.dedup();
29
- Ok(paths)
8
+ collect_tracked_and_untracked_paths(root, RepoPathFallback::Empty)
30
9
  }
31
10
 
32
11
  pub(super) fn evidence(paths: &[String], markers: &[&str]) -> Vec<String> {
@@ -21,12 +21,18 @@ pub(super) fn validate_task_state_projection_is_current(
21
21
  if !read::active_path(root).is_file() {
22
22
  return Ok(());
23
23
  }
24
+ if super::local_runtime_ledger_is_ignored(root) {
25
+ return Ok(());
26
+ }
24
27
  let Some(legacy) = read::read_legacy_task_state(root)? else {
25
28
  return Ok(());
26
29
  };
27
30
  let Some(rendered) = render::render_from_ledger(root)? else {
28
31
  return Ok(());
29
32
  };
33
+ if super::legacy_completed_projection_covers_local_runtime_ledger(&rendered.state, &legacy) {
34
+ return Ok(());
35
+ }
30
36
  if legacy != rendered.state {
31
37
  errors.push(".naome/task-state.json is stale relative to .naome/tasks/. Run node .naome/bin/naome.js task render-state --write --json before completion or commit.".to_string());
32
38
  }
@@ -10,6 +10,7 @@ use std::path::Path;
10
10
  use serde_json::Value;
11
11
 
12
12
  use crate::models::NaomeError;
13
+ use crate::paths;
13
14
 
14
15
  pub use model::{TaskLedgerProjection, TaskLedgerStatus};
15
16
 
@@ -21,10 +22,21 @@ pub fn migrate_task_state_to_ledger(
21
22
  }
22
23
 
23
24
  pub fn read_task_state_projection(root: &Path) -> Result<Option<Value>, NaomeError> {
24
- if let Some(projection) = render::render_from_ledger(root)? {
25
+ let legacy = read::read_legacy_task_state(root)?;
26
+ if legacy.is_some() && local_runtime_ledger_is_ignored(root) {
27
+ return Ok(legacy);
28
+ }
29
+ let rendered = render::render_from_ledger(root)?;
30
+ if let (Some(projection), Some(legacy_state)) = (&rendered, &legacy) {
31
+ if legacy_completed_projection_covers_local_runtime_ledger(&projection.state, legacy_state)
32
+ {
33
+ return Ok(Some(legacy_state.clone()));
34
+ }
35
+ }
36
+ if let Some(projection) = rendered {
25
37
  return Ok(Some(projection.state));
26
38
  }
27
- read::read_legacy_task_state(root)
39
+ Ok(legacy)
28
40
  }
29
41
 
30
42
  pub fn render_task_state_from_ledger(
@@ -46,3 +58,39 @@ pub(crate) fn validate_task_state_projection_is_current(
46
58
  ) -> Result<(), NaomeError> {
47
59
  write::validate_task_state_projection_is_current(root, errors)
48
60
  }
61
+
62
+ pub(crate) fn legacy_completed_projection_covers_local_runtime_ledger(
63
+ rendered: &Value,
64
+ legacy: &Value,
65
+ ) -> bool {
66
+ if state_status(legacy) != Some("complete") || state_status(rendered) != Some("complete") {
67
+ return false;
68
+ }
69
+
70
+ let Some(legacy_task) = legacy.get("activeTask") else {
71
+ return false;
72
+ };
73
+ let Some(rendered_task) = rendered.get("activeTask") else {
74
+ return false;
75
+ };
76
+
77
+ has_compact_proof_evidence(legacy_task) && !has_compact_proof_evidence(rendered_task)
78
+ }
79
+
80
+ pub(crate) fn local_runtime_ledger_is_ignored(root: &Path) -> bool {
81
+ let ignored_patterns = paths::naomeignore_patterns(root);
82
+ paths::matches_any(".naome/tasks/active.json", &ignored_patterns)
83
+ }
84
+
85
+ fn state_status(state: &Value) -> Option<&str> {
86
+ state.get("status").and_then(Value::as_str)
87
+ }
88
+
89
+ fn has_compact_proof_evidence(active_task: &Value) -> bool {
90
+ ["proofResults", "proofBatches"].iter().any(|field| {
91
+ active_task
92
+ .get(*field)
93
+ .and_then(Value::as_array)
94
+ .is_some_and(|entries| !entries.is_empty())
95
+ })
96
+ }
@@ -43,18 +43,7 @@ pub(super) fn require_string_array(
43
43
  field_name: &str,
44
44
  errors: &mut Vec<String>,
45
45
  ) {
46
- let Some(values) = value.and_then(Value::as_array) else {
47
- errors.push(format!("{field_name} must be a non-empty string array."));
48
- return;
49
- };
50
-
51
- if values.is_empty()
52
- || values
53
- .iter()
54
- .any(|entry| !entry.as_str().is_some_and(is_non_empty_string))
55
- {
56
- errors.push(format!("{field_name} must be a non-empty string array."));
57
- }
46
+ require_string_array_shape(value, field_name, errors, false);
58
47
  }
59
48
 
60
49
  pub(super) fn require_string_array_allow_empty(
@@ -62,16 +51,31 @@ pub(super) fn require_string_array_allow_empty(
62
51
  field_name: &str,
63
52
  errors: &mut Vec<String>,
64
53
  ) {
54
+ require_string_array_shape(value, field_name, errors, true);
55
+ }
56
+
57
+ fn require_string_array_shape(
58
+ value: Option<&Value>,
59
+ field_name: &str,
60
+ errors: &mut Vec<String>,
61
+ allow_empty: bool,
62
+ ) {
63
+ let label = if allow_empty {
64
+ "string array"
65
+ } else {
66
+ "non-empty string array"
67
+ };
65
68
  let Some(values) = value.and_then(Value::as_array) else {
66
- errors.push(format!("{field_name} must be a string array."));
69
+ errors.push(format!("{field_name} must be a {label}."));
67
70
  return;
68
71
  };
69
72
 
70
- if values
73
+ let invalid_empty = !allow_empty && values.is_empty();
74
+ let invalid_entry = values
71
75
  .iter()
72
- .any(|entry| !entry.as_str().is_some_and(is_non_empty_string))
73
- {
74
- errors.push(format!("{field_name} must be a string array."));
76
+ .any(|entry| !entry.as_str().is_some_and(is_non_empty_string));
77
+ if invalid_empty || invalid_entry {
78
+ errors.push(format!("{field_name} must be a {label}."));
75
79
  }
76
80
  }
77
81
 
@@ -7,7 +7,7 @@ use serde_json::json;
7
7
 
8
8
  mod repo_support;
9
9
 
10
- use repo_support::TestRepo;
10
+ use repo_support::{minimal_task_state, TestRepo};
11
11
 
12
12
  static ENV_LOCK: Mutex<()> = Mutex::new(());
13
13
 
@@ -193,13 +193,5 @@ fn online_checks_use_explicit_node_binary_from_environment() {
193
193
  }
194
194
 
195
195
  fn completed_without_proof_state() -> serde_json::Value {
196
- json!({
197
- "status": "complete",
198
- "activeTask": {
199
- "id": "readme-task",
200
- "allowedPaths": ["README.md"],
201
- "requiredCheckIds": ["diff-check"],
202
- "proofResults": []
203
- }
204
- })
196
+ minimal_task_state("complete", "readme-task", "README.md", "diff-check")
205
197
  }
@@ -21,17 +21,14 @@ fn classifies_naome_information_by_restore_source() {
21
21
  assert_eq!(local.restore_policy, "recreate_on_demand");
22
22
 
23
23
  let proof = classify_information_path(".naome/tasks/readme-task/proofs/diff-check.json");
24
- assert_eq!(proof.class, "run_evidence");
25
- assert_eq!(proof.commit_policy, "prefer_compact_release_evidence");
26
- assert_eq!(
27
- proof.restore_policy,
28
- "restore_from_ci_or_task_ledger_when_audited"
29
- );
24
+ assert_eq!(proof.class, "local_runtime_state");
25
+ assert_eq!(proof.commit_policy, "never_commit");
26
+ assert_eq!(proof.restore_policy, "recreate_on_demand");
30
27
 
31
28
  let task = classify_information_path(".naome/tasks/readme-task/task.json");
32
- assert_eq!(task.class, "durable_task_ledger");
33
- assert_eq!(task.commit_policy, "commit");
34
- assert_eq!(task.restore_policy, "restore_from_repository");
29
+ assert_eq!(task.class, "local_runtime_state");
30
+ assert_eq!(task.commit_policy, "never_commit");
31
+ assert_eq!(task.restore_policy, "recreate_on_demand");
35
32
  }
36
33
 
37
34
  #[test]
@@ -54,5 +51,5 @@ fn reports_information_architecture_contract_for_agents() {
54
51
  assert!(report
55
52
  .rules
56
53
  .iter()
57
- .any(|rule| rule.path_pattern == ".naome/tasks/*/proofs/*.json"));
54
+ .any(|rule| rule.path_pattern == ".naome/tasks/**" && rule.class == "local_runtime_state"));
58
55
  }
@@ -30,6 +30,7 @@ fn install_plan_includes_git_exclude_and_untrack_policy() {
30
30
  .contains(&".naome/task-journal.jsonl"));
31
31
  assert!(plan.git_exclude_entries.contains(&".naome/archive/"));
32
32
  assert!(plan.git_exclude_entries.contains(&".naome/cache/"));
33
+ assert!(plan.git_exclude_entries.contains(&".naome/tasks/"));
33
34
  assert!(plan.git_exclude_entries.contains(&".naome/bin/naome-rust*"));
34
35
  assert!(plan
35
36
  .git_exclude_entries
@@ -39,6 +40,7 @@ fn install_plan_includes_git_exclude_and_untrack_policy() {
39
40
  .contains(&".naome/bin/check-harness-health.js"));
40
41
  assert!(plan.git_untrack_paths.contains(&".naome/archive"));
41
42
  assert!(plan.git_untrack_paths.contains(&".naome/cache"));
43
+ assert!(plan.git_untrack_paths.contains(&".naome/tasks"));
42
44
  assert!(plan.git_untrack_paths.contains(&".naome/bin/naome-rust"));
43
45
  assert!(plan
44
46
  .git_untrack_paths
@@ -1,3 +1,5 @@
1
+ #![allow(dead_code)]
2
+
1
3
  use std::fs;
2
4
  use std::path::{Path, PathBuf};
3
5
  use std::process::Command;
@@ -126,31 +128,10 @@ impl StructureFixture {
126
128
  #[allow(dead_code)]
127
129
  impl StructureFixture {
128
130
  pub fn write_structure_config(&self, overrides: serde_json::Value) {
129
- let mut config = json!({
130
- "schema": "naome.repository-structure.v1",
131
- "version": 1,
132
- "status": "ready",
133
- "enabledAdapters": [],
134
- "sourceRoots": ["src/**"],
135
- "testRoots": ["tests/**", "test/**"],
136
- "docsRoots": ["docs/**"],
137
- "generatedRoots": ["**/generated/**"],
138
- "artifactRoots": ["dist/**", "build/**"],
139
- "moduleRoots": ["src/**"],
140
- "allowedRootFiles": ["README.md", "LICENSE", "package.json", "Cargo.toml"],
141
- "directoryRoleRules": [],
142
- "layerRules": [],
143
- "ignoredPaths": [],
144
- "disabledChecks": [],
145
- "changedCodePolicy": "block",
146
- "debtPolicy": "report",
147
- "limits": {
148
- "maxDirectoryFiles": 40,
149
- "maxPathDepth": 10,
150
- "maxDirectoryRoles": 2,
151
- "maxDumpingGroundFiles": 8
152
- }
153
- });
131
+ let mut config: serde_json::Value = serde_json::from_str(include_str!(
132
+ "../../../../templates/naome-root/.naome/repository-structure.json"
133
+ ))
134
+ .unwrap();
154
135
  merge(&mut config, overrides);
155
136
  self.write(
156
137
  ".naome/repository-structure.json",
@@ -14,10 +14,8 @@ pub use routes::{
14
14
  try_route_readme_task,
15
15
  };
16
16
  pub use verification_values::{
17
- change_type, check_missing_last_verified_fixture, diff_check,
17
+ change_type, check_missing_last_verified_fixture, diff_check, minimal_task_state,
18
18
  placeholder_verification_contract_fixture, quality_check, repo_docs_verification_fixture,
19
- repository_quality_config_source,
20
- repository_quality_config_value, repository_semantic_check,
21
- semantic_repository_quality_fixture_source,
22
- verification_value,
19
+ repository_quality_config_source, repository_quality_config_value, repository_semantic_check,
20
+ semantic_repository_quality_fixture_source, verification_value,
23
21
  };
@@ -3,6 +3,7 @@ use std::fs;
3
3
  use serde_json::json;
4
4
 
5
5
  use super::repo::TestRepo;
6
+ use super::verification_values::minimal_task_state;
6
7
 
7
8
  impl TestRepo {
8
9
  pub fn product_quality_repo(name: &str) -> Self {
@@ -69,15 +70,7 @@ impl TestRepo {
69
70
  pub fn write_implementing_task_state(&self, allowed_path: &str, check_id: &str) {
70
71
  self.write_naome_json(
71
72
  "task-state.json",
72
- json!({
73
- "status": "implementing",
74
- "activeTask": {
75
- "id": "rust-task",
76
- "allowedPaths": [allowed_path],
77
- "requiredCheckIds": [check_id],
78
- "proofResults": []
79
- }
80
- }),
73
+ minimal_task_state("implementing", "rust-task", allowed_path, check_id),
81
74
  );
82
75
  }
83
76
 
@@ -15,14 +15,7 @@ impl TestRepo {
15
15
  self.write_naome_json("upgrade-state.json", json!({ "status": "complete" }));
16
16
  self.write_naome_json(
17
17
  "verification.json",
18
- json!({
19
- "schema": "naome.verification.v1",
20
- "version": 1,
21
- "status": "ready",
22
- "checks": [diff_check(vec!["README.md"])],
23
- "changeTypes": [],
24
- "releaseGates": []
25
- }),
18
+ verification_value("ready", vec![diff_check(vec!["README.md"])], vec![]),
26
19
  );
27
20
  self.write_naome_json("task-state.json", task_state);
28
21
  }
@@ -74,28 +74,30 @@ pub fn verification_value(
74
74
  })
75
75
  }
76
76
 
77
- pub fn repository_quality_config_value() -> serde_json::Value {
77
+ pub fn minimal_task_state(
78
+ status: &str,
79
+ task_id: &str,
80
+ allowed_path: &str,
81
+ check_id: &str,
82
+ ) -> serde_json::Value {
78
83
  json!({
79
- "schema": "naome.repository-quality.v1",
80
- "version": 1,
81
- "status": "ready",
82
- "limits": {
83
- "maxFileLines": 450,
84
- "maxNewFileLines": 300,
85
- "maxDiffAddedLines": 180,
86
- "maxFunctionLines": 100,
87
- "maxTopLevelSymbols": 30,
88
- "duplicateBlockLines": 10,
89
- "nearDuplicateSimilarity": 0.9
90
- },
91
- "enabledAdapters": [],
92
- "disabledChecks": [],
93
- "ignoredPaths": [],
94
- "generatedPaths": [],
95
- "pathRules": []
84
+ "status": status,
85
+ "activeTask": {
86
+ "id": task_id,
87
+ "allowedPaths": [allowed_path],
88
+ "requiredCheckIds": [check_id],
89
+ "proofResults": []
90
+ }
96
91
  })
97
92
  }
98
93
 
94
+ pub fn repository_quality_config_value() -> serde_json::Value {
95
+ serde_json::from_str(include_str!(
96
+ "../../../../templates/naome-root/.naome/repository-quality.json"
97
+ ))
98
+ .unwrap()
99
+ }
100
+
99
101
  pub fn repository_quality_config_source() -> String {
100
102
  format!(
101
103
  "{}\n",
@@ -190,6 +190,70 @@ fn task_state_validation_reports_stale_rendered_projection_when_ledger_exists()
190
190
  }));
191
191
  }
192
192
 
193
+ #[test]
194
+ fn ignored_local_runtime_ledger_does_not_stale_committed_projection() {
195
+ let repo = TestRepo::new("ignored-runtime-ledger");
196
+ repo.init_git();
197
+ repo.write_file(".naomeignore", ".naome/archive/\n.naome/tasks/\n");
198
+ repo.write_file("README.md", "# Baseline\n");
199
+ repo.write_base_harness(Some(idle_state()));
200
+ repo.git(&["add", "."]);
201
+ repo.git(&["commit", "-m", "baseline"]);
202
+ let head = repo.git_stdout(&["rev-parse", "HEAD"]);
203
+ repo.write_complete_ledger(&head);
204
+ repo.write_naome_json("task-state.json", compact_complete_state(&head));
205
+ repo.write_file("README.md", "# Changed\n");
206
+
207
+ let report = validate_task_state(repo.path(), TaskStateOptions::default()).unwrap();
208
+
209
+ assert!(report.errors.is_empty(), "{:?}", report.errors);
210
+ }
211
+
212
+ #[test]
213
+ fn compact_completed_task_state_survives_incomplete_local_runtime_ledger() {
214
+ let repo = TestRepo::new("compact-over-runtime-ledger");
215
+ repo.init_git();
216
+ repo.write_file("README.md", "# Baseline\n");
217
+ repo.write_base_harness(Some(idle_state()));
218
+ repo.git(&["add", "."]);
219
+ repo.git(&["commit", "-m", "baseline"]);
220
+ let head = repo.git_stdout(&["rev-parse", "HEAD"]);
221
+ repo.write_naome_json("task-state.json", compact_complete_state(&head));
222
+ repo.write_file(
223
+ ".naome/tasks/active.json",
224
+ &format_json(object([
225
+ ("schema", json!("naome.task-ledger-active.v1")),
226
+ ("version", json!(1)),
227
+ ("primaryTaskId", json!("old-local-task")),
228
+ (
229
+ "worklanes",
230
+ json!([{ "id": "default", "taskId": "old-local-task", "status": "active" }]),
231
+ ),
232
+ ])),
233
+ );
234
+ repo.write_file(
235
+ ".naome/tasks/old-local-task/task.json",
236
+ &format_json(task_spec_record_with_id(&head, "old-local-task")),
237
+ );
238
+ repo.write_file(
239
+ ".naome/tasks/old-local-task/events.jsonl",
240
+ concat!(
241
+ "{\"schema\":\"naome.task-ledger-event.v1\",\"type\":\"status\",\"status\":\"implementing\",\"recordedAt\":\"2026-05-08T00:00:01.000Z\"}\n",
242
+ "{\"schema\":\"naome.task-ledger-event.v1\",\"type\":\"status\",\"status\":\"complete\",\"recordedAt\":\"2026-05-08T00:00:02.000Z\"}\n"
243
+ ),
244
+ );
245
+ repo.write_file("README.md", "# Changed\n");
246
+
247
+ let projected = read_task_state_projection(repo.path()).unwrap().unwrap();
248
+
249
+ assert_eq!(
250
+ projected["activeTask"]["proofBatches"][0]["proofs"][0]["checkId"],
251
+ "diff-check"
252
+ );
253
+ let report = validate_task_state(repo.path(), TaskStateOptions::default()).unwrap();
254
+ assert!(report.errors.is_empty(), "{:?}", report.errors);
255
+ }
256
+
193
257
  trait LedgerHarness {
194
258
  fn write_base_harness(&self, task_state: Option<Value>);
195
259
  fn write_complete_ledger(&self, admission_head: &str);
@@ -298,10 +362,14 @@ fn compact_active_task(admission_head: &str) -> Value {
298
362
  }
299
363
 
300
364
  fn task_spec_record(admission_head: &str) -> Value {
365
+ task_spec_record_with_id(admission_head, "readme-task")
366
+ }
367
+
368
+ fn task_spec_record_with_id(admission_head: &str, task_id: &str) -> Value {
301
369
  object([
302
370
  ("schema", json!("naome.task-ledger-task.v1")),
303
371
  ("version", json!(1)),
304
- ("id", json!("readme-task")),
372
+ ("id", json!(task_id)),
305
373
  ("request", json!("Update README.")),
306
374
  ("userPrompt", user_prompt()),
307
375
  ("admission", admission_record(admission_head)),
@@ -129,14 +129,16 @@ fn base_state(admission_head: &str, proof_payload: Value) -> Value {
129
129
  task.as_object_mut()
130
130
  .unwrap()
131
131
  .extend(proof_payload.as_object().unwrap().clone());
132
- json!({
133
- "schema": "naome.task-state.v2",
134
- "version": 2,
135
- "status": "complete",
136
- "activeTask": task,
137
- "blocker": null,
138
- "updatedAt": T0
139
- })
132
+ let mut state: Value = serde_json::from_str(include_str!(
133
+ "../../../../templates/naome-root/.naome/task-state.json"
134
+ ))
135
+ .unwrap();
136
+ state["schema"] = json!("naome.task-state.v2");
137
+ state["version"] = json!(2);
138
+ state["status"] = json!("complete");
139
+ state["activeTask"] = task;
140
+ state["updatedAt"] = json!(T0);
141
+ state
140
142
  }
141
143
 
142
144
  fn proof(check_id: &str, command: &str, cwd: &str) -> Value {
@@ -13,14 +13,14 @@ pub fn complete_task_state(overrides: Value) -> Value {
13
13
  }
14
14
 
15
15
  fn task_state_record(status: &str, active_task: Value, updated_at: Value) -> Value {
16
- json!({
17
- "schema": "naome.task-state.v1",
18
- "version": 1,
19
- "status": status,
20
- "activeTask": active_task,
21
- "blocker": null,
22
- "updatedAt": updated_at
23
- })
16
+ let mut state: Value = serde_json::from_str(include_str!(
17
+ "../../../../templates/naome-root/.naome/task-state.json"
18
+ ))
19
+ .unwrap();
20
+ state["status"] = json!(status);
21
+ state["activeTask"] = active_task;
22
+ state["updatedAt"] = updated_at;
23
+ state
24
24
  }
25
25
 
26
26
  pub fn active_task(overrides: Value) -> Value {
@@ -1,3 +1,5 @@
1
+ #![allow(dead_code)]
2
+
1
3
  use std::fs;
2
4
  use std::path::{Path, PathBuf};
3
5
  use std::process::Command;
@@ -1,3 +1,5 @@
1
+ import { posix } from "node:path";
2
+
1
3
  import { ensureArchiveDirectory, copyTemplateFile, walk } from "./filesystem.js";
2
4
  import {
3
5
  ensureBuiltInVerificationChecks,
@@ -19,7 +21,7 @@ import { printError } from "./output.js";
19
21
  import { compareVersions } from "./version.js";
20
22
  import { confirmAgentsTakeover, takeoverExistingAgents } from "./agents.js";
21
23
 
22
- const legacyCodexHookPaths = [
24
+ const legacyOptionalHookPaths = [
23
25
  ".codex/config.toml",
24
26
  ".codex/hooks.json",
25
27
  ".naome/bin/codex-hook-io.js",
@@ -63,10 +65,14 @@ export async function runExistingInstall(ctx, existingInstall) {
63
65
  }
64
66
 
65
67
  ensureArchiveDirectory(ctx);
66
- await runRepair(ctx, existingInstall.version, { fromVersion: existingInstall.version });
68
+ await runRepair(ctx, existingInstall.version, {
69
+ existingManifest: existingInstall.manifest,
70
+ fromVersion: existingInstall.version,
71
+ retireLegacyOptionalHooks: true,
72
+ });
67
73
  } else {
68
74
  ensureArchiveDirectory(ctx);
69
- await runRepair(ctx, existingInstall.version);
75
+ await runRepair(ctx, existingInstall.version, { existingManifest: existingInstall.manifest });
70
76
  }
71
77
  }
72
78
 
@@ -89,14 +95,57 @@ async function runRepair(ctx, version, options = {}) {
89
95
  ensureBuiltInVerificationChecks(ctx);
90
96
  ensureTestingProofHarnessSections(ctx);
91
97
  ensureRepositoryStructurePolicyFiles(ctx);
92
- removeLegacyCodexHookFiles(ctx, `repair-${version}`);
98
+ removeRetiredMachineOwnedFiles(ctx, options.existingManifest, `repair-${version}`, {
99
+ extraPaths: options.retireLegacyOptionalHooks ? legacyOptionalHookPaths : [],
100
+ });
93
101
  refreshManifestHealthMetadata(ctx);
94
102
  ensureCompleteUpgradeState(ctx, options.fromVersion ?? null);
95
103
  ensureLocalOnlySourceControlBoundary(ctx);
96
104
  }
97
105
 
98
- function removeLegacyCodexHookFiles(ctx, archiveDirName) {
99
- for (const relativePath of legacyCodexHookPaths) {
106
+ function removeRetiredMachineOwnedFiles(ctx, manifest, archiveDirName, options = {}) {
107
+ const currentOwnedPaths = new Set([
108
+ ...ctx.machineOwnedPaths,
109
+ ...ctx.projectOwnedPaths,
110
+ ...ctx.localOnlyMachineOwnedPaths,
111
+ ctx.nativeBinaryRelativePath,
112
+ ].filter(Boolean));
113
+ const retiredPaths = [
114
+ ...(Array.isArray(manifest?.machineOwned) ? manifest.machineOwned : []),
115
+ ...(Array.isArray(options.extraPaths) ? options.extraPaths : []),
116
+ ];
117
+
118
+ for (const relativePath of [...new Set(retiredPaths)]) {
119
+ if (!isSafeManifestPath(relativePath)) {
120
+ const pathLabel = String(relativePath);
121
+ ctx.skipped.push(pathLabel);
122
+ ctx.unsafeSkipped.push(pathLabel);
123
+ continue;
124
+ }
125
+
126
+ if (currentOwnedPaths.has(relativePath)) {
127
+ continue;
128
+ }
129
+
100
130
  removeLegacyHarnessFile(ctx, relativePath, archiveDirName);
101
131
  }
102
132
  }
133
+
134
+ function isSafeManifestPath(relativePath) {
135
+ if (
136
+ typeof relativePath !== "string" ||
137
+ relativePath.length === 0 ||
138
+ relativePath.includes("\0") ||
139
+ relativePath.includes("\\") ||
140
+ relativePath.includes(":")
141
+ ) {
142
+ return false;
143
+ }
144
+
145
+ if (posix.isAbsolute(relativePath)) {
146
+ return false;
147
+ }
148
+
149
+ const normalizedPath = posix.normalize(relativePath);
150
+ return normalizedPath === relativePath && normalizedPath !== "." && !normalizedPath.startsWith("../");
151
+ }
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamentis/naome",
3
- "version": "1.3.5",
3
+ "version": "1.3.7",
4
4
  "description": "Native-first CLI for the NAOME agent harness.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -10,7 +10,6 @@
10
10
  "ai",
11
11
  "harness",
12
12
  "repository",
13
- "codex",
14
13
  "claude"
15
14
  ],
16
15
  "repository": {
@@ -8,12 +8,12 @@
8
8
  ".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
9
9
  ".naome/task-contract.schema.json": "sha256:1b3b62350328d0d6d660e36d1d1baaa2b88718530db774f9ab2a9e2fcba369c8",
10
10
  "AGENTS.md": "sha256:e8b2fc786c1c72b69ba8f2b2ffce4f459e799c7453ce9ff4a9f6448a8f9e6b4f",
11
- "docs/naome/agent-workflow.md": "sha256:2fd1fd02eb6849133b9e8227421914580b1c469a60388a063c1b6ed48016b48d",
11
+ "docs/naome/agent-workflow.md": "sha256:cbef6dee1543b4c74111f8dc23e81a5bf092a9585a342504678bfc9c9d0655ea",
12
12
  "docs/naome/context-economy.md": "sha256:3ed5075815ecf4ada46a5e65438769310307c35759fcd46b13dc0b96e02bebd9",
13
13
  "docs/naome/execution.md": "sha256:bfc5d55838942ec8e3d790b59e3c634ff5bf6a2298265cef3dca9788a097eafb",
14
14
  "docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
15
15
  "docs/naome/index.md": "sha256:07ef776f49130319a5280bdb3ae38af22141708253f38eb983a4336fbae1b25a",
16
- "docs/naome/task-ledger.md": "sha256:ac637a31abdd13eee15a49086594e63f5c88fe12a5cf621b227310788ae7e583",
16
+ "docs/naome/task-ledger.md": "sha256:6ca7222c80079b4662fb718d3c71d686770646f1fa52b83b0e90aed1c5a1101b",
17
17
  "docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1"
18
18
  },
19
19
  "machineOwned": [
@@ -3,3 +3,4 @@
3
3
 
4
4
  .naome/archive/
5
5
  .naome/cache/
6
+ .naome/tasks/
@@ -110,22 +110,20 @@ Use this workflow after first-run intake is complete.
110
110
  1. Identify changed files.
111
111
  2. Match them against `.naome/verification.json`.
112
112
  3. Run the required checks when available.
113
- 4. Prefer ledger-backed task updates. `naome sync` migrates active legacy
113
+ 4. Prefer local ledger-backed task updates. `naome sync` migrates active legacy
114
114
  task-state automatically; if a legacy-only active task is still present, run
115
115
  `node .naome/bin/naome.js task migrate-ledger --write --json` before adding
116
116
  completion proof.
117
- 5. Record proof as compact `proofPathSets` and `proofBatches` in
118
- `.naome/tasks/<task-id>/task.json` when many checks share evidence paths.
119
- Use `.naome/tasks/<task-id>/proofs/` only when separate per-check files are
120
- needed for parallel writers or durable audit detail. If legacy compatibility
121
- work is unavoidable, use the same compact fields in `.naome/task-state.json`
122
- and include every changed in-scope path reported by git in expanded proof
123
- evidence.
117
+ 5. Record proof as compact `proofPathSets` and `proofBatches` in the local
118
+ ledger when many checks share evidence paths. Use local per-check proof files
119
+ only when separate writers need them during the run. The committed release
120
+ artifact is `.naome/task-state.json`; include every changed in-scope path
121
+ reported by git in expanded proof evidence.
124
122
  6. Run `node .naome/bin/naome.js task render-state --write --json` before
125
123
  external compatibility checks. Do not hand-edit the rendered projection when
126
124
  `.naome/tasks/active.json` exists.
127
- 7. Do not list `.naome/task-state.json` or `.naome/tasks/` as task scope or
128
- proof evidence.
125
+ 7. Do not list `.naome/task-state.json` or local task runtime folders as task
126
+ scope or proof evidence.
129
127
  8. Stop tracked long-running processes or mark them explicitly allowed in
130
128
  `.naome/processes.json`.
131
129
  9. Set task status to `complete` in the ledger event stream or legacy task
@@ -8,16 +8,16 @@ Status: Uninitialized
8
8
 
9
9
  ## Known Boundaries
10
10
 
11
- - NAOME information falls into durable project state, durable task ledger,
12
- generated projection, local runtime state, run evidence, and product source.
11
+ - NAOME information falls into durable project state, generated projection,
12
+ local runtime state, run evidence, and product source.
13
13
  - Durable project state is committed repository or package state needed to
14
14
  reinstall or reconstruct the harness.
15
- - `.naome/task-state.json` is a generated compatibility projection when
16
- `.naome/tasks/active.json` exists. Render it from the ledger instead of
17
- hand-editing it.
18
- - `.naome/tmp/` is local runtime state and must not be committed.
19
- - Per-check proof files are run evidence. Prefer compact ledger proof batches
20
- when many release checks share the same evidence paths.
15
+ - `.naome/task-state.json` is the committed compact task-state projection.
16
+ - `.naome/tmp/` and `.naome/tasks/` are local runtime state and must not be
17
+ committed.
18
+ - Per-check proof files are local run evidence. Prefer compact proof batches in
19
+ the committed task-state projection when many release checks share the same
20
+ evidence paths.
21
21
 
22
22
  ## Assumed Boundaries
23
23
 
@@ -1,8 +1,9 @@
1
1
  # Task Ledger
2
2
 
3
- NAOME task state is moving from one mutable aggregate file to a canonical task
4
- ledger. The compatibility file `.naome/task-state.json` remains supported, but
5
- new deterministic task tooling can use `.naome/tasks/` as the source of truth.
3
+ NAOME task state is moving from one mutable aggregate file to a local task
4
+ ledger. The compatibility file `.naome/task-state.json` remains the compact
5
+ committed projection, while `.naome/tasks/` is machine-local runtime state for
6
+ efficient task updates.
6
7
 
7
8
  ## Layout
8
9
 
@@ -33,9 +34,10 @@ new deterministic task tooling can use `.naome/tasks/` as the source of truth.
33
34
  ## Compatibility
34
35
 
35
36
  Existing `naome.task-state.v1` and `naome.task-state.v2` files remain valid. If
36
- `.naome/tasks/active.json` exists, NAOME readers build a canonical task model
37
- from the ledger and render a `naome.task-state.v2` projection. If no ledger is
38
- present, readers fall back to `.naome/task-state.json`.
37
+ a current local ledger exists, NAOME readers build a canonical task model from
38
+ it and render a `naome.task-state.v2` projection. If no local ledger is present,
39
+ or an older local completed ledger lacks proof detail that the committed compact
40
+ projection still has, readers fall back to `.naome/task-state.json`.
39
41
 
40
42
  Use this command when an external tool needs the compatibility file refreshed:
41
43
 
@@ -44,21 +46,22 @@ node .naome/bin/naome.js task render-state --write --json
44
46
  ```
45
47
 
46
48
  `naome sync` automatically splits an active legacy `task-state` file into the
47
- ledger layout. The explicit command remains available for repair or tests:
49
+ local ledger layout. The explicit command remains available for repair or
50
+ tests:
48
51
 
49
52
  ```text
50
53
  node .naome/bin/naome.js task migrate-ledger --write --json
51
54
  ```
52
55
 
53
56
  After `.naome/tasks/active.json` exists, `.naome/task-state.json` is a rendered
54
- compatibility projection. NAOME gates reject a stale projection and report the
55
- render command instead of accepting hand-edited aggregate state.
57
+ compatibility projection. NAOME gates reject a stale active projection and
58
+ report the render command instead of accepting hand-edited aggregate state.
56
59
 
57
60
  ## Conflict Policy
58
61
 
59
- `.naome/tasks/` is NAOME control state. It is not task feature scope and is not
60
- required as proof evidence. Gates ignore ledger control files when checking
61
- whether product changes stay inside `allowedPaths`.
62
+ `.naome/tasks/` is local NAOME runtime state. It is ignored, untracked by
63
+ `naome sync`, and not task feature scope or proof evidence. Gates ignore ledger
64
+ control files when checking whether product changes stay inside `allowedPaths`.
62
65
 
63
66
  The intended long-term model is:
64
67
 
@@ -76,8 +79,8 @@ instead of all competing for `.naome/task-state.json`.
76
79
  ## Information Policy
77
80
 
78
81
  Use `node .naome/bin/naome.js workflow information --path <path> --json` before
79
- changing NAOME control files whose retention is unclear. Durable configuration,
80
- templates, task specs, and task events are restored from repository or package
81
- state. `.naome/task-state.json` is generated from durable state. `.naome/tmp/`
82
- is local runtime state. Per-check proof files are run evidence; compact release
83
- proof batches keep release diffs readable without losing verification coverage.
82
+ changing NAOME control files whose retention is unclear. Durable configuration
83
+ and templates are restored from repository or package state.
84
+ `.naome/task-state.json` is the committed compact task projection. `.naome/tmp/`
85
+ and `.naome/tasks/` are local runtime state. Compact release proof batches keep
86
+ release diffs readable without tracking per-task runtime folders.