@lamentis/naome 1.3.3 → 1.3.5
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 +0 -5
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/main.rs +1 -0
- package/crates/naome-cli/src/workflow_commands.rs +30 -4
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/information_architecture.rs +179 -0
- package/crates/naome-core/src/install_plan.rs +0 -12
- package/crates/naome-core/src/lib.rs +5 -0
- package/crates/naome-core/src/route/git_ops.rs +50 -5
- package/crates/naome-core/src/task_ledger/render.rs +50 -16
- package/crates/naome-core/tests/information_architecture.rs +58 -0
- package/crates/naome-core/tests/install_plan.rs +0 -11
- package/crates/naome-core/tests/task_ledger.rs +62 -0
- package/installer/context.js +0 -10
- package/installer/filesystem.js +0 -4
- package/installer/flows.js +18 -4
- package/installer/harness-file-ops.js +4 -0
- package/installer/install-plan.js +0 -4
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +1 -1
- package/templates/naome-root/.naome/bin/check-harness-health.js +3 -3
- package/templates/naome-root/.naome/bin/check-task-state.js +3 -3
- package/templates/naome-root/.naome/bin/naome.js +18 -47
- package/templates/naome-root/.naome/manifest.json +3 -3
- package/templates/naome-root/docs/naome/agent-workflow.md +7 -4
- package/templates/naome-root/docs/naome/architecture.md +11 -2
- package/templates/naome-root/docs/naome/task-ledger.md +13 -1
- package/templates/naome-root/docs/naome/testing.md +0 -4
- package/installer/codex-hooks.js +0 -121
- package/templates/naome-root/.codex/config.toml +0 -2
- package/templates/naome-root/.codex/hooks.json +0 -70
- package/templates/naome-root/.naome/bin/codex-hook-io.js +0 -122
- package/templates/naome-root/.naome/bin/codex-hook-policy.js +0 -180
- package/templates/naome-root/.naome/bin/codex-hook-runtime.js +0 -174
- package/templates/naome-root/.naome/bin/codex-hook.js +0 -6
- package/templates/naome-root/docs/naome/codex-hooks.md +0 -82
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.5"
|
|
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.5"
|
|
88
88
|
dependencies = [
|
|
89
89
|
"serde",
|
|
90
90
|
"serde_json",
|
package/README.md
CHANGED
|
@@ -50,8 +50,6 @@ naome route --prompt-file /path/to/prompt.txt --execute --json
|
|
|
50
50
|
perfect on day one.
|
|
51
51
|
- Records verification proof before a task can be treated as complete.
|
|
52
52
|
- Keeps sync fast by making baseline and deep quality scans explicit.
|
|
53
|
-
- Can install optional Codex hooks for earlier agent feedback without making
|
|
54
|
-
hooks a human workflow requirement.
|
|
55
53
|
|
|
56
54
|
## Safety Model
|
|
57
55
|
|
|
@@ -99,8 +97,6 @@ After sync, NAOME writes the agent-facing workflow into `docs/naome/`:
|
|
|
99
97
|
- `docs/naome/testing.md` maps change types to required checks.
|
|
100
98
|
- `docs/naome/repository-quality.md` explains quality, structure, and cleanup
|
|
101
99
|
policy.
|
|
102
|
-
- `docs/naome/codex-hooks.md` explains the optional Codex hook acceleration
|
|
103
|
-
layer.
|
|
104
100
|
|
|
105
101
|
Agents should follow the repository's NAOME docs instead of guessing workflow
|
|
106
102
|
rules from generic project files.
|
|
@@ -116,7 +112,6 @@ The main local policy files are:
|
|
|
116
112
|
- `.naome/repository-structure.json` for path role, module, and directory
|
|
117
113
|
structure policy.
|
|
118
114
|
- `.naome/task-state.json` for active task state and proof.
|
|
119
|
-
- `.codex/hooks.json` only when optional Codex hooks were explicitly enabled.
|
|
120
115
|
|
|
121
116
|
Product defaults stay generic. Repository-specific policy belongs in the local
|
|
122
117
|
`.naome/` config files.
|
|
@@ -69,6 +69,7 @@ const HELP: &str = r#"Usage:
|
|
|
69
69
|
naome workflow phases [--json]
|
|
70
70
|
naome workflow processes [--json]
|
|
71
71
|
naome workflow mutations --path <path> [path...] [--json]
|
|
72
|
+
naome workflow information [--path <path>] [--json]
|
|
72
73
|
naome check-harness-health [--root <path>] [--allow-missing-archive]
|
|
73
74
|
naome check-task-state [--root <path>] [--admission|--progress|--commit-gate|--push-gate] [--allow-missing-archive]
|
|
74
75
|
naome validate-verification [--root <path>]"#;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
use std::path::Path;
|
|
2
2
|
|
|
3
3
|
use naome_core::{
|
|
4
|
-
agent_run_plan, classify_mutations, context_delta_report,
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
agent_run_plan, classify_information_path, classify_mutations, context_delta_report,
|
|
5
|
+
decision_gate, doctor_report, edit_session_watchdog, information_architecture_report,
|
|
6
|
+
proof_plan_for_task, refresh_integrity, repository_capability_graph, safe_rg_args,
|
|
7
|
+
summarize_command_output, tracked_process_report, validate_search_command,
|
|
7
8
|
verification_phase_plan,
|
|
8
9
|
};
|
|
9
10
|
|
|
@@ -32,7 +33,7 @@ pub fn run_workflow_command(
|
|
|
32
33
|
args: &[String],
|
|
33
34
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
34
35
|
let Some(subcommand) = args.get(1).map(String::as_str) else {
|
|
35
|
-
return Err("naome workflow requires agent-plan, context-delta, proof-plan, capabilities, edit-watchdog, decision-gate, digest, search-profile, check-search, phases, processes, or
|
|
36
|
+
return Err("naome workflow requires agent-plan, context-delta, proof-plan, capabilities, edit-watchdog, decision-gate, digest, search-profile, check-search, phases, processes, mutations, or information.".into());
|
|
36
37
|
};
|
|
37
38
|
let json = args.iter().any(|arg| arg == "--json");
|
|
38
39
|
|
|
@@ -103,6 +104,7 @@ pub fn run_workflow_command(
|
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
"mutations" => run_mutations(root, args, json)?,
|
|
107
|
+
"information" => run_information(args, json)?,
|
|
106
108
|
_ => return Err(format!("unknown naome workflow command: {subcommand}").into()),
|
|
107
109
|
}
|
|
108
110
|
Ok(())
|
|
@@ -179,6 +181,30 @@ fn run_mutations(
|
|
|
179
181
|
Ok(())
|
|
180
182
|
}
|
|
181
183
|
|
|
184
|
+
fn run_information(args: &[String], json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
|
185
|
+
if let Some(path) = option_value(args, "--path") {
|
|
186
|
+
let classification = classify_information_path(&path);
|
|
187
|
+
if json {
|
|
188
|
+
println!("{}", serde_json::to_string_pretty(&classification)?);
|
|
189
|
+
} else {
|
|
190
|
+
println!(
|
|
191
|
+
"{}: {} ({}, {})",
|
|
192
|
+
classification.path,
|
|
193
|
+
classification.class,
|
|
194
|
+
classification.commit_policy,
|
|
195
|
+
classification.restore_policy
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return Ok(());
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
print_json_or_label(
|
|
202
|
+
serde_json::to_value(information_architecture_report())?,
|
|
203
|
+
json,
|
|
204
|
+
"NAOME information architecture ready.",
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
182
208
|
fn run_context_delta(
|
|
183
209
|
root: &Path,
|
|
184
210
|
args: &[String],
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
use serde::Serialize;
|
|
2
|
+
|
|
3
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
|
4
|
+
pub struct InformationArchitectureReport {
|
|
5
|
+
pub schema: &'static str,
|
|
6
|
+
pub version: u8,
|
|
7
|
+
pub classes: Vec<InformationClass>,
|
|
8
|
+
pub rules: Vec<InformationPathRule>,
|
|
9
|
+
}
|
|
10
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
|
11
|
+
pub struct InformationClass {
|
|
12
|
+
pub id: &'static str,
|
|
13
|
+
pub description: &'static str,
|
|
14
|
+
pub commit_policy: &'static str,
|
|
15
|
+
pub restore_policy: &'static str,
|
|
16
|
+
}
|
|
17
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
|
18
|
+
pub struct InformationPathRule {
|
|
19
|
+
pub path_pattern: &'static str,
|
|
20
|
+
pub class: &'static str,
|
|
21
|
+
pub commit_policy: &'static str,
|
|
22
|
+
pub restore_policy: &'static str,
|
|
23
|
+
}
|
|
24
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
|
25
|
+
pub struct InformationPathClassification {
|
|
26
|
+
pub path: String,
|
|
27
|
+
pub class: &'static str,
|
|
28
|
+
pub commit_policy: &'static str,
|
|
29
|
+
pub restore_policy: &'static str,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const CLASSES: &[(&str, &str, &str, &str)] = &[
|
|
33
|
+
(
|
|
34
|
+
"durable_project_state",
|
|
35
|
+
"Committed configuration, policy, and templates required to reconstruct the harness.",
|
|
36
|
+
"commit",
|
|
37
|
+
"restore_from_repository_or_package",
|
|
38
|
+
),
|
|
39
|
+
(
|
|
40
|
+
"durable_task_ledger",
|
|
41
|
+
"Committed task intent, scope, and lifecycle records.",
|
|
42
|
+
"commit",
|
|
43
|
+
"restore_from_repository",
|
|
44
|
+
),
|
|
45
|
+
(
|
|
46
|
+
"generated_projection",
|
|
47
|
+
"Compatibility views generated from durable state.",
|
|
48
|
+
"commit_when_required_by_compatibility",
|
|
49
|
+
"regenerate_from_durable_state",
|
|
50
|
+
),
|
|
51
|
+
(
|
|
52
|
+
"local_runtime_state",
|
|
53
|
+
"Machine-local prompts, caches, temporary files, and runtime state.",
|
|
54
|
+
"never_commit",
|
|
55
|
+
"recreate_on_demand",
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
"run_evidence",
|
|
59
|
+
"Verification outputs from a specific run.",
|
|
60
|
+
"prefer_compact_release_evidence",
|
|
61
|
+
"restore_from_ci_or_task_ledger_when_audited",
|
|
62
|
+
),
|
|
63
|
+
(
|
|
64
|
+
"product_source",
|
|
65
|
+
"Repository source, package metadata, tests, scripts, and documentation.",
|
|
66
|
+
"commit_when_in_task_scope",
|
|
67
|
+
"restore_from_repository",
|
|
68
|
+
),
|
|
69
|
+
];
|
|
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" },
|
|
87
|
+
];
|
|
88
|
+
pub fn information_architecture_report() -> InformationArchitectureReport {
|
|
89
|
+
InformationArchitectureReport {
|
|
90
|
+
schema: "naome.information-architecture.v1",
|
|
91
|
+
version: 1,
|
|
92
|
+
classes: CLASSES
|
|
93
|
+
.iter()
|
|
94
|
+
.map(|entry| class_from_tuple(*entry))
|
|
95
|
+
.collect(),
|
|
96
|
+
rules: RULES
|
|
97
|
+
.iter()
|
|
98
|
+
.map(|rule| rule_from_class(rule.path_pattern, rule.class))
|
|
99
|
+
.collect(),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pub fn classify_information_path(path: &str) -> InformationPathClassification {
|
|
104
|
+
let path = normalize_path(path);
|
|
105
|
+
let class_id = RULES
|
|
106
|
+
.iter()
|
|
107
|
+
.find(|rule| matches_information_rule(&path, rule.path_pattern))
|
|
108
|
+
.map(|rule| rule.class)
|
|
109
|
+
.unwrap_or("product_source");
|
|
110
|
+
let class = class_by_id(class_id);
|
|
111
|
+
InformationPathClassification {
|
|
112
|
+
path,
|
|
113
|
+
class: class.id,
|
|
114
|
+
commit_policy: class.commit_policy,
|
|
115
|
+
restore_policy: class.restore_policy,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn rule_from_class(path_pattern: &'static str, class_id: &'static str) -> InformationPathRule {
|
|
120
|
+
let class = class_by_id(class_id);
|
|
121
|
+
InformationPathRule {
|
|
122
|
+
path_pattern,
|
|
123
|
+
class: class.id,
|
|
124
|
+
commit_policy: class.commit_policy,
|
|
125
|
+
restore_policy: class.restore_policy,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn class_by_id(id: &str) -> InformationClass {
|
|
130
|
+
CLASSES
|
|
131
|
+
.iter()
|
|
132
|
+
.find(|(class_id, _, _, _)| *class_id == id)
|
|
133
|
+
.map(|entry| class_from_tuple(*entry))
|
|
134
|
+
.expect("information class rule must reference a known class")
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fn class_from_tuple(
|
|
138
|
+
entry: (&'static str, &'static str, &'static str, &'static str),
|
|
139
|
+
) -> InformationClass {
|
|
140
|
+
InformationClass {
|
|
141
|
+
id: entry.0,
|
|
142
|
+
description: entry.1,
|
|
143
|
+
commit_policy: entry.2,
|
|
144
|
+
restore_policy: entry.3,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn normalize_path(path: &str) -> String {
|
|
149
|
+
path.replace('\\', "/")
|
|
150
|
+
.trim_start_matches("./")
|
|
151
|
+
.trim_end_matches('/')
|
|
152
|
+
.to_string()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fn matches_information_rule(path: &str, pattern: &str) -> bool {
|
|
156
|
+
if let Some(prefix) = pattern.strip_suffix("/**") {
|
|
157
|
+
return path == prefix || path.starts_with(&format!("{prefix}/"));
|
|
158
|
+
}
|
|
159
|
+
if let Some(prefix) = pattern
|
|
160
|
+
.strip_suffix("*.json")
|
|
161
|
+
.filter(|prefix| !prefix.contains('*'))
|
|
162
|
+
{
|
|
163
|
+
return path.starts_with(prefix) && path.ends_with(".json");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let path_parts: Vec<&str> = path.split('/').collect();
|
|
167
|
+
let pattern_parts: Vec<&str> = pattern.split('/').collect();
|
|
168
|
+
path_parts.len() == pattern_parts.len()
|
|
169
|
+
&& path_parts
|
|
170
|
+
.iter()
|
|
171
|
+
.zip(pattern_parts.iter())
|
|
172
|
+
.all(|(path_part, pattern_part)| {
|
|
173
|
+
*pattern_part == "*"
|
|
174
|
+
|| *path_part == *pattern_part
|
|
175
|
+
|| pattern_part
|
|
176
|
+
.strip_prefix('*')
|
|
177
|
+
.is_some_and(|suffix| path_part.ends_with(suffix))
|
|
178
|
+
})
|
|
179
|
+
}
|
|
@@ -60,16 +60,6 @@ pub const LOCAL_NATIVE_BINARY_PATHS: &[&str] = &[
|
|
|
60
60
|
".naome/task-journal.jsonl",
|
|
61
61
|
];
|
|
62
62
|
|
|
63
|
-
pub const OPTIONAL_CODEX_HOOK_PATHS: &[&str] = &[
|
|
64
|
-
".codex/config.toml",
|
|
65
|
-
".codex/hooks.json",
|
|
66
|
-
".naome/bin/codex-hook.js",
|
|
67
|
-
".naome/bin/codex-hook-io.js",
|
|
68
|
-
".naome/bin/codex-hook-policy.js",
|
|
69
|
-
".naome/bin/codex-hook-runtime.js",
|
|
70
|
-
"docs/naome/codex-hooks.md",
|
|
71
|
-
];
|
|
72
|
-
|
|
73
63
|
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
|
|
74
64
|
#[serde(rename_all = "camelCase")]
|
|
75
65
|
pub struct InstallPlan {
|
|
@@ -78,7 +68,6 @@ pub struct InstallPlan {
|
|
|
78
68
|
pub machine_owned: Vec<&'static str>,
|
|
79
69
|
pub project_owned: Vec<&'static str>,
|
|
80
70
|
pub local_only_machine_owned: Vec<&'static str>,
|
|
81
|
-
pub optional_codex_hook_paths: Vec<&'static str>,
|
|
82
71
|
pub gitignore_entries: Vec<&'static str>,
|
|
83
72
|
pub git_exclude_entries: Vec<&'static str>,
|
|
84
73
|
pub git_untrack_paths: Vec<&'static str>,
|
|
@@ -104,7 +93,6 @@ pub fn install_plan(harness_version: impl Into<String>) -> InstallPlan {
|
|
|
104
93
|
machine_owned: MACHINE_OWNED_PATHS.to_vec(),
|
|
105
94
|
project_owned: PROJECT_OWNED_PATHS.to_vec(),
|
|
106
95
|
local_only_machine_owned: LOCAL_ONLY_MACHINE_OWNED_PATHS.to_vec(),
|
|
107
|
-
optional_codex_hook_paths: OPTIONAL_CODEX_HOOK_PATHS.to_vec(),
|
|
108
96
|
gitignore_entries,
|
|
109
97
|
git_exclude_entries,
|
|
110
98
|
git_untrack_paths,
|
|
@@ -2,6 +2,7 @@ mod context;
|
|
|
2
2
|
mod decision;
|
|
3
3
|
mod git;
|
|
4
4
|
mod harness_health;
|
|
5
|
+
mod information_architecture;
|
|
5
6
|
mod install_plan;
|
|
6
7
|
mod intent;
|
|
7
8
|
mod journal;
|
|
@@ -23,6 +24,10 @@ pub use context::{
|
|
|
23
24
|
};
|
|
24
25
|
pub use decision::{evaluate_decision, format_decision, EvaluationOptions};
|
|
25
26
|
pub use harness_health::{validate_harness_health, HarnessHealthOptions};
|
|
27
|
+
pub use information_architecture::{
|
|
28
|
+
classify_information_path, information_architecture_report, InformationArchitectureReport,
|
|
29
|
+
InformationClass, InformationPathClassification, InformationPathRule,
|
|
30
|
+
};
|
|
26
31
|
pub use install_plan::{install_plan, InstallPlan};
|
|
27
32
|
pub use install_plan::{MACHINE_OWNED_PATHS, PROJECT_OWNED_PATHS};
|
|
28
33
|
pub use intent::{evaluate_intent, format_intent, IntentDecision, PromptEvidence};
|
|
@@ -37,8 +37,25 @@ fn git_add_paths(root: &Path, paths: &[String]) -> Result<(), NaomeError> {
|
|
|
37
37
|
if paths.is_empty() {
|
|
38
38
|
return Ok(());
|
|
39
39
|
}
|
|
40
|
+
let tracked_paths = git_ls_files(root, &[], paths)?;
|
|
41
|
+
if !tracked_paths.is_empty() {
|
|
42
|
+
let mut update_args = vec!["add", "-u", "--"];
|
|
43
|
+
update_args.extend(tracked_paths.iter().map(String::as_str));
|
|
44
|
+
ensure_git_success(
|
|
45
|
+
Command::new("git")
|
|
46
|
+
.args(update_args)
|
|
47
|
+
.current_dir(root)
|
|
48
|
+
.output()?,
|
|
49
|
+
)?;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let untracked_paths = git_ls_files(root, &["--others", "--exclude-standard"], paths)?;
|
|
53
|
+
if untracked_paths.is_empty() {
|
|
54
|
+
return Ok(());
|
|
55
|
+
}
|
|
56
|
+
|
|
40
57
|
let mut args = vec!["add", "--"];
|
|
41
|
-
args.extend(
|
|
58
|
+
args.extend(untracked_paths.iter().map(String::as_str));
|
|
42
59
|
let output = Command::new("git").args(args).current_dir(root).output()?;
|
|
43
60
|
if output.status.success() {
|
|
44
61
|
Ok(())
|
|
@@ -47,6 +64,31 @@ fn git_add_paths(root: &Path, paths: &[String]) -> Result<(), NaomeError> {
|
|
|
47
64
|
}
|
|
48
65
|
}
|
|
49
66
|
|
|
67
|
+
fn git_ls_files(
|
|
68
|
+
root: &Path,
|
|
69
|
+
mode_args: &[&str],
|
|
70
|
+
paths: &[String],
|
|
71
|
+
) -> Result<Vec<String>, NaomeError> {
|
|
72
|
+
let mut args = vec!["ls-files"];
|
|
73
|
+
args.extend(mode_args);
|
|
74
|
+
args.extend(["-z", "--"]);
|
|
75
|
+
args.extend(paths.iter().map(String::as_str));
|
|
76
|
+
let output = Command::new("git").args(args).current_dir(root).output()?;
|
|
77
|
+
if output.status.success() {
|
|
78
|
+
Ok(split_nul_paths(&output.stdout))
|
|
79
|
+
} else {
|
|
80
|
+
Err(NaomeError::new(command_output(&output)))
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn split_nul_paths(bytes: &[u8]) -> Vec<String> {
|
|
85
|
+
String::from_utf8_lossy(bytes)
|
|
86
|
+
.split('\0')
|
|
87
|
+
.filter(|path| !path.is_empty())
|
|
88
|
+
.map(ToString::to_string)
|
|
89
|
+
.collect()
|
|
90
|
+
}
|
|
91
|
+
|
|
50
92
|
pub(super) fn git_commit(root: &Path, message: &str) -> Result<(), NaomeError> {
|
|
51
93
|
ensure_git_success(git_output(root, &["commit", "-m", message])?)
|
|
52
94
|
}
|
|
@@ -64,9 +106,12 @@ pub(super) fn ensure_git_success(output: Output) -> Result<(), NaomeError> {
|
|
|
64
106
|
}
|
|
65
107
|
|
|
66
108
|
pub(super) fn command_output(output: &std::process::Output) -> String {
|
|
67
|
-
let stderr = String::from_utf8_lossy(&output.stderr)
|
|
68
|
-
|
|
69
|
-
|
|
109
|
+
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
110
|
+
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
111
|
+
for message in [stderr.trim(), stdout.trim()] {
|
|
112
|
+
if !message.is_empty() {
|
|
113
|
+
return message.to_string();
|
|
114
|
+
}
|
|
70
115
|
}
|
|
71
|
-
|
|
116
|
+
format!("git exited with status {}", output.status)
|
|
72
117
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
use std::path::Path;
|
|
2
2
|
|
|
3
|
-
use serde_json::{json, Value};
|
|
3
|
+
use serde_json::{json, Map, Value};
|
|
4
4
|
|
|
5
5
|
use crate::models::NaomeError;
|
|
6
6
|
|
|
@@ -28,28 +28,62 @@ pub(super) fn render_from_ledger(root: &Path) -> Result<Option<TaskLedgerProject
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
fn render_active_task(spec: Value, proof_results: Vec<Value>) -> Value {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
31
|
+
let mut active_task = Map::new();
|
|
32
|
+
active_task.insert(
|
|
33
|
+
"id".to_string(),
|
|
34
|
+
spec.get("id").cloned().unwrap_or(Value::Null),
|
|
35
|
+
);
|
|
36
|
+
active_task.insert(
|
|
37
|
+
"request".to_string(),
|
|
38
|
+
spec.get("request").cloned().unwrap_or(Value::Null),
|
|
39
|
+
);
|
|
40
|
+
active_task.insert(
|
|
41
|
+
"userPrompt".to_string(),
|
|
42
|
+
spec.get("userPrompt").cloned().unwrap_or(Value::Null),
|
|
43
|
+
);
|
|
44
|
+
active_task.insert(
|
|
45
|
+
"admission".to_string(),
|
|
46
|
+
spec.get("admission").cloned().unwrap_or(Value::Null),
|
|
47
|
+
);
|
|
48
|
+
active_task.insert(
|
|
49
|
+
"allowedPaths".to_string(),
|
|
50
|
+
spec.get("allowedPaths")
|
|
39
51
|
.cloned()
|
|
40
52
|
.unwrap_or_else(|| json!([])),
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
);
|
|
54
|
+
active_task.insert(
|
|
55
|
+
"declaredChangeTypes".to_string(),
|
|
56
|
+
spec.get("declaredChangeTypes")
|
|
43
57
|
.cloned()
|
|
44
58
|
.unwrap_or_else(|| json!([])),
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
59
|
+
);
|
|
60
|
+
active_task.insert(
|
|
61
|
+
"requiredCheckIds".to_string(),
|
|
62
|
+
spec.get("requiredCheckIds")
|
|
63
|
+
.cloned()
|
|
64
|
+
.unwrap_or_else(|| json!([])),
|
|
65
|
+
);
|
|
66
|
+
active_task.insert("proofResults".to_string(), Value::Array(proof_results));
|
|
67
|
+
if let Some(path_sets) = spec.get("proofPathSets") {
|
|
68
|
+
active_task.insert("proofPathSets".to_string(), path_sets.clone());
|
|
69
|
+
}
|
|
70
|
+
if let Some(batches) = spec.get("proofBatches") {
|
|
71
|
+
active_task.insert("proofBatches".to_string(), batches.clone());
|
|
72
|
+
}
|
|
73
|
+
active_task.insert(
|
|
74
|
+
"revisions".to_string(),
|
|
75
|
+
spec.get("revisions").cloned().unwrap_or_else(|| json!([])),
|
|
76
|
+
);
|
|
77
|
+
active_task.insert(
|
|
78
|
+
"humanReview".to_string(),
|
|
79
|
+
spec.get("humanReview").cloned().unwrap_or_else(|| {
|
|
48
80
|
json!({
|
|
49
81
|
"required": false,
|
|
50
82
|
"approved": false,
|
|
51
83
|
"reason": null
|
|
52
84
|
})
|
|
53
|
-
})
|
|
54
|
-
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
Value::Object(active_task)
|
|
55
89
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
use naome_core::{classify_information_path, information_architecture_report};
|
|
2
|
+
|
|
3
|
+
#[test]
|
|
4
|
+
fn classifies_naome_information_by_restore_source() {
|
|
5
|
+
let durable = classify_information_path(".naome/verification.json");
|
|
6
|
+
assert_eq!(durable.class, "durable_project_state");
|
|
7
|
+
assert_eq!(durable.commit_policy, "commit");
|
|
8
|
+
assert_eq!(durable.restore_policy, "restore_from_repository_or_package");
|
|
9
|
+
|
|
10
|
+
let projection = classify_information_path(".naome/task-state.json");
|
|
11
|
+
assert_eq!(projection.class, "generated_projection");
|
|
12
|
+
assert_eq!(
|
|
13
|
+
projection.commit_policy,
|
|
14
|
+
"commit_when_required_by_compatibility"
|
|
15
|
+
);
|
|
16
|
+
assert_eq!(projection.restore_policy, "regenerate_from_durable_state");
|
|
17
|
+
|
|
18
|
+
let local = classify_information_path(".naome/tmp/route.prompt");
|
|
19
|
+
assert_eq!(local.class, "local_runtime_state");
|
|
20
|
+
assert_eq!(local.commit_policy, "never_commit");
|
|
21
|
+
assert_eq!(local.restore_policy, "recreate_on_demand");
|
|
22
|
+
|
|
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
|
+
);
|
|
30
|
+
|
|
31
|
+
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");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#[test]
|
|
38
|
+
fn reports_information_architecture_contract_for_agents() {
|
|
39
|
+
let report = information_architecture_report();
|
|
40
|
+
|
|
41
|
+
assert_eq!(report.schema, "naome.information-architecture.v1");
|
|
42
|
+
assert!(report
|
|
43
|
+
.classes
|
|
44
|
+
.iter()
|
|
45
|
+
.any(|class| class.id == "durable_project_state"));
|
|
46
|
+
assert!(report
|
|
47
|
+
.classes
|
|
48
|
+
.iter()
|
|
49
|
+
.any(|class| class.id == "generated_projection"));
|
|
50
|
+
assert!(report
|
|
51
|
+
.classes
|
|
52
|
+
.iter()
|
|
53
|
+
.any(|class| class.id == "run_evidence"));
|
|
54
|
+
assert!(report
|
|
55
|
+
.rules
|
|
56
|
+
.iter()
|
|
57
|
+
.any(|rule| rule.path_pattern == ".naome/tasks/*/proofs/*.json"));
|
|
58
|
+
}
|
|
@@ -19,17 +19,6 @@ fn install_plan_marks_machine_docs_and_bins_local_only() {
|
|
|
19
19
|
assert!(!plan
|
|
20
20
|
.local_only_machine_owned
|
|
21
21
|
.contains(&"docs/naome/architecture.md"));
|
|
22
|
-
assert!(plan
|
|
23
|
-
.optional_codex_hook_paths
|
|
24
|
-
.contains(&".codex/hooks.json"));
|
|
25
|
-
assert!(plan
|
|
26
|
-
.optional_codex_hook_paths
|
|
27
|
-
.contains(&".codex/config.toml"));
|
|
28
|
-
assert!(plan
|
|
29
|
-
.optional_codex_hook_paths
|
|
30
|
-
.contains(&".naome/bin/codex-hook.js"));
|
|
31
|
-
assert!(!plan.machine_owned.contains(&".naome/bin/codex-hook.js"));
|
|
32
|
-
assert!(!plan.project_owned.contains(&".codex/hooks.json"));
|
|
33
22
|
}
|
|
34
23
|
|
|
35
24
|
#[test]
|
|
@@ -119,6 +119,57 @@ fn task_state_v2_can_migrate_to_ledger_without_losing_compact_proof_data() {
|
|
|
119
119
|
assert!(report.errors.is_empty(), "{:?}", report.errors);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
#[test]
|
|
123
|
+
fn ledger_projection_preserves_compact_proof_batches_from_task_spec() {
|
|
124
|
+
let repo = TestRepo::new("compact-ledger");
|
|
125
|
+
repo.init_git();
|
|
126
|
+
repo.write_file("README.md", "# Baseline\n");
|
|
127
|
+
repo.write_base_harness(Some(idle_state()));
|
|
128
|
+
repo.git(&["add", "."]);
|
|
129
|
+
repo.git(&["commit", "-m", "baseline"]);
|
|
130
|
+
let head = repo.git_stdout(&["rev-parse", "HEAD"]);
|
|
131
|
+
|
|
132
|
+
repo.write_file(
|
|
133
|
+
".naome/tasks/active.json",
|
|
134
|
+
&format_json(object([
|
|
135
|
+
("schema", json!("naome.task-ledger-active.v1")),
|
|
136
|
+
("version", json!(1)),
|
|
137
|
+
("primaryTaskId", json!("readme-task")),
|
|
138
|
+
(
|
|
139
|
+
"worklanes",
|
|
140
|
+
json!([{ "id": "default", "taskId": "readme-task", "status": "active" }]),
|
|
141
|
+
),
|
|
142
|
+
])),
|
|
143
|
+
);
|
|
144
|
+
repo.write_file(
|
|
145
|
+
".naome/tasks/readme-task/task.json",
|
|
146
|
+
&format_json(compact_task_spec_record(&head)),
|
|
147
|
+
);
|
|
148
|
+
repo.write_file(
|
|
149
|
+
".naome/tasks/readme-task/events.jsonl",
|
|
150
|
+
concat!(
|
|
151
|
+
"{\"schema\":\"naome.task-ledger-event.v1\",\"type\":\"status\",\"status\":\"implementing\",\"recordedAt\":\"2026-05-08T00:00:01.000Z\"}\n",
|
|
152
|
+
"{\"schema\":\"naome.task-ledger-event.v1\",\"type\":\"status\",\"status\":\"complete\",\"recordedAt\":\"2026-05-08T00:00:02.000Z\"}\n"
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
repo.write_file("README.md", "# Changed\n");
|
|
156
|
+
|
|
157
|
+
let projected = read_task_state_projection(repo.path()).unwrap().unwrap();
|
|
158
|
+
assert_eq!(projected["activeTask"]["proofResults"], json!([]));
|
|
159
|
+
assert_eq!(
|
|
160
|
+
projected["activeTask"]["proofPathSets"]["changed"],
|
|
161
|
+
json!(["README.md"])
|
|
162
|
+
);
|
|
163
|
+
assert_eq!(
|
|
164
|
+
projected["activeTask"]["proofBatches"][0]["proofs"][0]["checkId"],
|
|
165
|
+
"diff-check"
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
render_task_state_from_ledger(repo.path(), true).unwrap();
|
|
169
|
+
let report = validate_task_state(repo.path(), TaskStateOptions::default()).unwrap();
|
|
170
|
+
assert!(report.errors.is_empty(), "{:?}", report.errors);
|
|
171
|
+
}
|
|
172
|
+
|
|
122
173
|
#[test]
|
|
123
174
|
fn task_state_validation_reports_stale_rendered_projection_when_ledger_exists() {
|
|
124
175
|
let repo = TestRepo::new("stale-projection");
|
|
@@ -261,6 +312,17 @@ fn task_spec_record(admission_head: &str) -> Value {
|
|
|
261
312
|
])
|
|
262
313
|
}
|
|
263
314
|
|
|
315
|
+
fn compact_task_spec_record(admission_head: &str) -> Value {
|
|
316
|
+
let mut task = task_spec_record(admission_head);
|
|
317
|
+
let task_object = task.as_object_mut().unwrap();
|
|
318
|
+
task_object.insert(
|
|
319
|
+
"proofPathSets".to_string(),
|
|
320
|
+
object([("changed", json!(["README.md"]))]),
|
|
321
|
+
);
|
|
322
|
+
task_object.insert("proofBatches".to_string(), json!([compact_proof_batch()]));
|
|
323
|
+
task
|
|
324
|
+
}
|
|
325
|
+
|
|
264
326
|
fn diff_proof_record() -> Value {
|
|
265
327
|
object([
|
|
266
328
|
("schema", json!("naome.task-ledger-proof.v1")),
|