@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.
- package/Cargo.lock +2 -2
- package/README.md +4 -3
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/git.rs +1 -26
- package/crates/naome-core/src/information_architecture.rs +15 -26
- package/crates/naome-core/src/install_plan.rs +2 -0
- package/crates/naome-core/src/lib.rs +1 -0
- package/crates/naome-core/src/paths.rs +27 -0
- package/crates/naome-core/src/quality/scanner/repo_paths.rs +2 -47
- package/crates/naome-core/src/repo_paths.rs +76 -0
- package/crates/naome-core/src/repository_model/path_scan.rs +2 -23
- package/crates/naome-core/src/task_ledger/write.rs +6 -0
- package/crates/naome-core/src/task_ledger.rs +50 -2
- package/crates/naome-core/src/task_state/util.rs +21 -17
- package/crates/naome-core/tests/decision.rs +2 -10
- package/crates/naome-core/tests/information_architecture.rs +7 -10
- package/crates/naome-core/tests/install_plan.rs +2 -0
- package/crates/naome-core/tests/quality_structure_support/mod.rs +6 -25
- package/crates/naome-core/tests/repo_support/mod.rs +3 -5
- package/crates/naome-core/tests/repo_support/repo_helpers.rs +2 -9
- package/crates/naome-core/tests/repo_support/verification.rs +1 -8
- package/crates/naome-core/tests/repo_support/verification_values.rs +20 -18
- package/crates/naome-core/tests/task_ledger.rs +69 -1
- package/crates/naome-core/tests/task_state_compact_support/states.rs +10 -8
- package/crates/naome-core/tests/task_state_support/states.rs +8 -8
- package/crates/naome-core/tests/workflow_support/mod.rs +2 -0
- package/installer/flows.js +55 -6
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +1 -2
- package/templates/naome-root/.naome/manifest.json +2 -2
- package/templates/naome-root/.naomeignore +1 -0
- package/templates/naome-root/docs/naome/agent-workflow.md +8 -10
- package/templates/naome-root/docs/naome/architecture.md +8 -8
- 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.
|
|
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.
|
|
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
|
|
88
|
-
|
|
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
|
|
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,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 =
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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(|
|
|
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(|
|
|
108
|
-
.map(|
|
|
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",
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
69
|
+
errors.push(format!("{field_name} must be a {label}."));
|
|
67
70
|
return;
|
|
68
71
|
};
|
|
69
72
|
|
|
70
|
-
|
|
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
|
|
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
|
-
|
|
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, "
|
|
25
|
-
assert_eq!(proof.commit_policy, "
|
|
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, "
|
|
33
|
-
assert_eq!(task.commit_policy, "
|
|
34
|
-
assert_eq!(task.restore_policy, "
|
|
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
|
|
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 =
|
|
130
|
-
"
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
"
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
|
|
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!(
|
|
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
|
-
|
|
133
|
-
"
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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 {
|
package/installer/flows.js
CHANGED
|
@@ -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
|
|
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, {
|
|
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
|
-
|
|
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
|
|
99
|
-
|
|
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
|
package/native/linux-x64/naome
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lamentis/naome",
|
|
3
|
-
"version": "1.3.
|
|
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:
|
|
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:
|
|
16
|
+
"docs/naome/task-ledger.md": "sha256:6ca7222c80079b4662fb718d3c71d686770646f1fa52b83b0e90aed1c5a1101b",
|
|
17
17
|
"docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1"
|
|
18
18
|
},
|
|
19
19
|
"machineOwned": [
|
|
@@ -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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
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,
|
|
12
|
-
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
4
|
-
ledger. The compatibility file `.naome/task-state.json` remains
|
|
5
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
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
|
|
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
|
|
60
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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.
|