@lamentis/naome 1.0.2 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +2 -2
- package/README.md +8 -1
- package/bin/naome-node.js +4 -1
- package/bin/naome.js +198 -3
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/main.rs +110 -13
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/decision.rs +82 -11
- package/crates/naome-core/src/git.rs +12 -1
- package/crates/naome-core/src/harness_health.rs +3 -1
- package/crates/naome-core/src/install_plan.rs +4 -2
- package/crates/naome-core/src/intent.rs +914 -0
- package/crates/naome-core/src/journal.rs +169 -0
- package/crates/naome-core/src/lib.rs +10 -1
- package/crates/naome-core/src/models.rs +63 -4
- package/crates/naome-core/src/route.rs +1063 -0
- package/crates/naome-core/src/task_state.rs +372 -21
- package/crates/naome-core/tests/decision.rs +8 -6
- package/crates/naome-core/tests/install_plan.rs +9 -1
- package/crates/naome-core/tests/intent.rs +826 -0
- package/crates/naome-core/tests/route.rs +1159 -0
- package/crates/naome-core/tests/task_state.rs +203 -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 +7 -6
- package/templates/naome-root/.naome/bin/check-task-state.js +7 -6
- package/templates/naome-root/.naome/bin/naome.js +143 -13
- package/templates/naome-root/.naome/manifest.json +8 -7
- package/templates/naome-root/.naome/upgrade-state.json +1 -1
- package/templates/naome-root/AGENTS.md +30 -5
- package/templates/naome-root/docs/naome/agent-workflow.md +45 -24
- package/templates/naome-root/docs/naome/execution.md +55 -51
- package/templates/naome-root/docs/naome/index.md +10 -3
package/Cargo.lock
CHANGED
|
@@ -76,7 +76,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
|
|
76
76
|
|
|
77
77
|
[[package]]
|
|
78
78
|
name = "naome-cli"
|
|
79
|
-
version = "1.
|
|
79
|
+
version = "1.1.1"
|
|
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.
|
|
87
|
+
version = "1.1.1"
|
|
88
88
|
dependencies = [
|
|
89
89
|
"serde",
|
|
90
90
|
"serde_json",
|
package/README.md
CHANGED
|
@@ -17,4 +17,11 @@ Set up a repository:
|
|
|
17
17
|
naome sync
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Keep the CLI current before refreshing an existing repo:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
naome update
|
|
24
|
+
naome sync
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then paste the printed first-run prompt into your coding agent. Use `naome status`, `naome next`, `naome route`, and `naome commit` to inspect state, route natural-language follow-ups, choose the next safe action, and baseline completed work.
|
package/bin/naome-node.js
CHANGED
|
@@ -58,7 +58,8 @@ Then read .naome/upgrade-state.json, .naome/task-state.json, and docs/naome/exec
|
|
|
58
58
|
Run node .naome/bin/check-harness-health.js before feature work.
|
|
59
59
|
If initialized is false, follow docs/naome/first-run.md.
|
|
60
60
|
Do not begin feature work until NAOME intake is complete.
|
|
61
|
-
After intake,
|
|
61
|
+
After intake, save the user's next natural-language request to a temporary prompt file, then run node .naome/bin/naome.js route --prompt-file <path> --execute --json.
|
|
62
|
+
Follow userMessage, humanOptions, requiredContext, and canCreateTask from the route output before creating the requested task.`;
|
|
62
63
|
|
|
63
64
|
let summaryTitle = "NAOME intake harness installed";
|
|
64
65
|
let nextStepPrompt = firstRunPrompt;
|
|
@@ -512,6 +513,7 @@ async function runFreshInstall() {
|
|
|
512
513
|
patchInstalledMachineOwnedIntegrity();
|
|
513
514
|
ensureBuiltInVerificationChecks();
|
|
514
515
|
patchManifestDate();
|
|
516
|
+
ensureCompleteUpgradeState(null);
|
|
515
517
|
ensureArchiveDirectory();
|
|
516
518
|
takeoverExistingAgents();
|
|
517
519
|
ensureLocalOnlySourceControlBoundary();
|
|
@@ -524,6 +526,7 @@ function runExistingInstall(existingInstall) {
|
|
|
524
526
|
printError("This repository already uses a newer NAOME harness.");
|
|
525
527
|
console.error(`Installed: v${existingInstall.version}`);
|
|
526
528
|
console.error(`Package: v${packageVersion}`);
|
|
529
|
+
console.error("Run `naome update`, then rerun `naome sync` from the repository root.");
|
|
527
530
|
process.exit(1);
|
|
528
531
|
}
|
|
529
532
|
|
package/bin/naome.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
8
|
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
9
|
+
const packageMetadata = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
10
|
+
const packageName = packageMetadata.name;
|
|
11
|
+
const packageVersion = packageMetadata.version;
|
|
9
12
|
const nativeBinaryName = process.platform === "win32" ? "naome.exe" : "naome";
|
|
10
13
|
const args = process.argv.slice(2);
|
|
11
14
|
const [command] = args;
|
|
@@ -15,8 +18,16 @@ if (isHelpRequest(args)) {
|
|
|
15
18
|
process.exit(0);
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
if (command === "update") {
|
|
22
|
+
runUpdateCommand(args.slice(1));
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
if (command === "install" || command === "sync") {
|
|
19
|
-
|
|
26
|
+
if (args.includes("--check-update")) {
|
|
27
|
+
requireCurrentCliBeforeSync();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
runNativePackageCommand(args.filter((arg) => arg !== "--check-update"));
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
const commandPath = findHarnessCommand(process.cwd());
|
|
@@ -42,11 +53,195 @@ function printHelp() {
|
|
|
42
53
|
console.log("Usage:");
|
|
43
54
|
console.log(" naome status [--json]");
|
|
44
55
|
console.log(" naome next [--json]");
|
|
56
|
+
console.log(" naome intent --prompt-file <path> [--json]");
|
|
57
|
+
console.log(" naome intent --prompt <text> [--json]");
|
|
58
|
+
console.log(" naome route --prompt-file <path> [--execute] [--json]");
|
|
59
|
+
console.log(" naome route --prompt <text> [--execute] [--json]");
|
|
60
|
+
console.log(" naome explain --prompt-file <path> [--json]");
|
|
61
|
+
console.log(" naome explain --prompt <text> [--json]");
|
|
45
62
|
console.log(" naome install");
|
|
46
|
-
console.log(" naome sync");
|
|
63
|
+
console.log(" naome sync [--check-update]");
|
|
64
|
+
console.log(" naome update [--json] [--execute]");
|
|
47
65
|
console.log(" naome commit -m \"type(scope): message\"");
|
|
48
66
|
}
|
|
49
67
|
|
|
68
|
+
function runUpdateCommand(commandArgs) {
|
|
69
|
+
const json = commandArgs.includes("--json");
|
|
70
|
+
const execute = commandArgs.includes("--execute");
|
|
71
|
+
|
|
72
|
+
if (json && execute) {
|
|
73
|
+
console.error("NAOME: update --execute does not support --json output.");
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const update = getUpdateState();
|
|
78
|
+
if (!update.ok) {
|
|
79
|
+
if (json) {
|
|
80
|
+
console.log(JSON.stringify(formatUpdateState(update), null, 2));
|
|
81
|
+
} else {
|
|
82
|
+
console.error("NAOME CLI update check failed.");
|
|
83
|
+
console.error(update.error);
|
|
84
|
+
}
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!execute) {
|
|
89
|
+
if (json) {
|
|
90
|
+
console.log(JSON.stringify(formatUpdateState(update), null, 2));
|
|
91
|
+
} else if (update.outdated) {
|
|
92
|
+
printUpdateInstructions(update);
|
|
93
|
+
} else {
|
|
94
|
+
console.log(`NAOME CLI is current: v${update.currentVersion}.`);
|
|
95
|
+
}
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!update.outdated) {
|
|
100
|
+
console.log(`NAOME CLI is current: v${update.currentVersion}.`);
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
printUpdateInstructions(update);
|
|
105
|
+
const result = spawnSync("npm", ["install", "-g", `${packageName}@latest`, "--force"], {
|
|
106
|
+
cwd: process.cwd(),
|
|
107
|
+
encoding: "utf8",
|
|
108
|
+
stdio: "inherit"
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (result.status !== 0) {
|
|
112
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log("NAOME CLI updated. Run `naome sync` from the repository root.");
|
|
116
|
+
process.exit(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function requireCurrentCliBeforeSync() {
|
|
120
|
+
const update = getUpdateState();
|
|
121
|
+
|
|
122
|
+
if (!update.ok) {
|
|
123
|
+
console.error("NAOME CLI update check failed.");
|
|
124
|
+
console.error(update.error);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!update.outdated) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.error("NAOME CLI update required before sync.");
|
|
133
|
+
console.error(`Installed CLI: v${update.currentVersion}`);
|
|
134
|
+
console.error(`Latest CLI: v${update.latestVersion}`);
|
|
135
|
+
console.error(`Run: ${update.installCommand}`);
|
|
136
|
+
console.error(`Then: ${update.nextCommand}`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getUpdateState() {
|
|
141
|
+
const latest = getLatestPackageVersion();
|
|
142
|
+
if (!latest.ok) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
packageName,
|
|
146
|
+
currentVersion: packageVersion,
|
|
147
|
+
latestVersion: null,
|
|
148
|
+
outdated: false,
|
|
149
|
+
installCommand: `npm install -g ${packageName}@latest --force`,
|
|
150
|
+
nextCommand: "naome sync",
|
|
151
|
+
error: latest.error
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
packageName,
|
|
158
|
+
currentVersion: packageVersion,
|
|
159
|
+
latestVersion: latest.version,
|
|
160
|
+
outdated: compareVersions(packageVersion, latest.version) < 0,
|
|
161
|
+
installCommand: `npm install -g ${packageName}@latest --force`,
|
|
162
|
+
nextCommand: "naome sync"
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function formatUpdateState(update) {
|
|
167
|
+
return {
|
|
168
|
+
schema: "naome.update.v1",
|
|
169
|
+
ok: update.ok,
|
|
170
|
+
packageName: update.packageName,
|
|
171
|
+
currentVersion: update.currentVersion,
|
|
172
|
+
latestVersion: update.latestVersion,
|
|
173
|
+
outdated: update.outdated,
|
|
174
|
+
installCommand: update.installCommand,
|
|
175
|
+
nextCommand: update.nextCommand,
|
|
176
|
+
...(update.ok ? {} : { error: update.error })
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function printUpdateInstructions(update) {
|
|
181
|
+
console.log(`NAOME update available: v${update.currentVersion} -> v${update.latestVersion}.`);
|
|
182
|
+
console.log(`Run: ${update.installCommand}`);
|
|
183
|
+
console.log(`Then: ${update.nextCommand}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getLatestPackageVersion() {
|
|
187
|
+
if (process.env.NAOME_TEST_NPM_LATEST_VERSION) {
|
|
188
|
+
return parseLatestVersion(process.env.NAOME_TEST_NPM_LATEST_VERSION);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = spawnSync("npm", ["view", packageName, "version", "--json"], {
|
|
192
|
+
cwd: process.cwd(),
|
|
193
|
+
encoding: "utf8"
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (result.status !== 0) {
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
error: (result.stderr || result.stdout || "npm view failed").trim()
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return parseLatestVersion(result.stdout);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function parseLatestVersion(value) {
|
|
207
|
+
let version = String(value).trim();
|
|
208
|
+
if (!version) {
|
|
209
|
+
return { ok: false, error: "npm returned an empty package version." };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const parsed = JSON.parse(version);
|
|
214
|
+
if (typeof parsed === "string") {
|
|
215
|
+
version = parsed;
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
// npm can also emit the bare version when registry tooling changes output.
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!isSemver(version)) {
|
|
222
|
+
return { ok: false, error: `npm returned an invalid package version: ${version}` };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return { ok: true, version };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function compareVersions(left, right) {
|
|
229
|
+
const leftParts = left.split(".").map(Number);
|
|
230
|
+
const rightParts = right.split(".").map(Number);
|
|
231
|
+
|
|
232
|
+
for (let index = 0; index < 3; index += 1) {
|
|
233
|
+
if (leftParts[index] !== rightParts[index]) {
|
|
234
|
+
return leftParts[index] < rightParts[index] ? -1 : 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function isSemver(version) {
|
|
242
|
+
return /^\d+\.\d+\.\d+$/.test(version);
|
|
243
|
+
}
|
|
244
|
+
|
|
50
245
|
function runNativePackageCommand(args) {
|
|
51
246
|
const nativeBinary = resolveNativePackageBinary();
|
|
52
247
|
if (!nativeBinary) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
use std::collections::HashMap;
|
|
2
|
+
use std::fs;
|
|
2
3
|
use std::path::{Path, PathBuf};
|
|
3
4
|
use std::process::Command;
|
|
4
5
|
|
|
@@ -29,6 +30,11 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
29
30
|
|
|
30
31
|
if command != "status"
|
|
31
32
|
&& command != "next"
|
|
33
|
+
&& command != "intent"
|
|
34
|
+
&& command != "route"
|
|
35
|
+
&& command != "explain"
|
|
36
|
+
&& command != "journal-task"
|
|
37
|
+
&& command != "commit-paths"
|
|
32
38
|
&& command != "seed-verification"
|
|
33
39
|
&& command != "install-plan"
|
|
34
40
|
&& command != "install"
|
|
@@ -163,6 +169,84 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
163
169
|
std::process::exit(1);
|
|
164
170
|
}
|
|
165
171
|
|
|
172
|
+
if command == "intent" {
|
|
173
|
+
let prompt = prompt_from_args(&args)?;
|
|
174
|
+
let intent = naome_core::evaluate_intent(&root, &prompt, EvaluationOptions::online())?;
|
|
175
|
+
if args.iter().any(|arg| arg == "--json") {
|
|
176
|
+
println!("{}", serde_json::to_string_pretty(&intent)?);
|
|
177
|
+
} else {
|
|
178
|
+
print!("{}", naome_core::format_intent(&intent));
|
|
179
|
+
}
|
|
180
|
+
return Ok(());
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if command == "route" {
|
|
184
|
+
let prompt = prompt_from_args(&args)?;
|
|
185
|
+
let route = naome_core::evaluate_route(
|
|
186
|
+
&root,
|
|
187
|
+
&prompt,
|
|
188
|
+
naome_core::RouteOptions {
|
|
189
|
+
execute: args.iter().any(|arg| arg == "--execute"),
|
|
190
|
+
evaluation: EvaluationOptions::online(),
|
|
191
|
+
},
|
|
192
|
+
)?;
|
|
193
|
+
if args.iter().any(|arg| arg == "--json") {
|
|
194
|
+
println!("{}", serde_json::to_string_pretty(&route)?);
|
|
195
|
+
} else {
|
|
196
|
+
println!("{}", route.user_message);
|
|
197
|
+
if !route.human_options.is_empty() {
|
|
198
|
+
println!("Human options: {}", route.human_options.join(", "));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return Ok(());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if command == "explain" {
|
|
205
|
+
let prompt = prompt_from_args(&args)?;
|
|
206
|
+
let explain = naome_core::explain_route(&root, &prompt, EvaluationOptions::online())?;
|
|
207
|
+
if args.iter().any(|arg| arg == "--json") {
|
|
208
|
+
println!("{}", serde_json::to_string_pretty(&explain)?);
|
|
209
|
+
} else {
|
|
210
|
+
println!("NAOME explain: {}", explain.winning_rule);
|
|
211
|
+
println!("Repo state: {}", explain.repo_state);
|
|
212
|
+
println!("Prompt intent: {}", explain.prompt_intent);
|
|
213
|
+
println!("Would mutate: {}", explain.would_mutate);
|
|
214
|
+
println!("Message: {}", explain.user_message);
|
|
215
|
+
if !explain.human_options.is_empty() {
|
|
216
|
+
println!("Human options: {}", explain.human_options.join(", "));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return Ok(());
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if command == "journal-task" {
|
|
223
|
+
let outcome =
|
|
224
|
+
option_value(&args, "--outcome").unwrap_or_else(|| "naome_commit_baseline".to_string());
|
|
225
|
+
let commit_before = option_value(&args, "--commit-before");
|
|
226
|
+
let commit_after = option_value(&args, "--commit-after");
|
|
227
|
+
let entry = naome_core::append_task_journal(&root, &outcome, commit_before, commit_after)?;
|
|
228
|
+
if args.iter().any(|arg| arg == "--json") {
|
|
229
|
+
println!("{}", serde_json::to_string_pretty(&entry)?);
|
|
230
|
+
} else if entry.is_some() {
|
|
231
|
+
println!("NAOME task journal updated.");
|
|
232
|
+
} else {
|
|
233
|
+
println!("NAOME task journal had no active task to record.");
|
|
234
|
+
}
|
|
235
|
+
return Ok(());
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if command == "commit-paths" {
|
|
239
|
+
let paths = naome_core::completed_task_commit_paths(&root)?;
|
|
240
|
+
if args.iter().any(|arg| arg == "--json") {
|
|
241
|
+
println!("{}", serde_json::to_string_pretty(&paths)?);
|
|
242
|
+
} else {
|
|
243
|
+
for path in paths {
|
|
244
|
+
println!("{path}");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return Ok(());
|
|
248
|
+
}
|
|
249
|
+
|
|
166
250
|
let decision = evaluate_decision(&root, EvaluationOptions::online())?;
|
|
167
251
|
|
|
168
252
|
if args.iter().any(|arg| arg == "--json") {
|
|
@@ -175,10 +259,12 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
175
259
|
}
|
|
176
260
|
|
|
177
261
|
fn is_help_request(args: &[String]) -> bool {
|
|
178
|
-
matches!(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
262
|
+
matches!(
|
|
263
|
+
args.first().map(String::as_str),
|
|
264
|
+
Some("help" | "--help" | "-h")
|
|
265
|
+
) || args
|
|
266
|
+
.get(1)
|
|
267
|
+
.is_some_and(|arg| arg == "--help" || arg == "-h" || arg == "help")
|
|
182
268
|
}
|
|
183
269
|
|
|
184
270
|
fn find_harness_root(start: &Path) -> Option<PathBuf> {
|
|
@@ -211,6 +297,12 @@ fn print_help() {
|
|
|
211
297
|
println!("Usage:");
|
|
212
298
|
println!(" naome status [--json]");
|
|
213
299
|
println!(" naome next [--json]");
|
|
300
|
+
println!(" naome intent --prompt-file <path> [--json]");
|
|
301
|
+
println!(" naome intent --prompt <text> [--json]");
|
|
302
|
+
println!(" naome route --prompt-file <path> [--execute] [--json]");
|
|
303
|
+
println!(" naome route --prompt <text> [--execute] [--json]");
|
|
304
|
+
println!(" naome explain --prompt-file <path> [--json]");
|
|
305
|
+
println!(" naome explain --prompt <text> [--json]");
|
|
214
306
|
println!(" naome install [--package-root <path>] [--installer-js <path>]");
|
|
215
307
|
println!(" naome sync [--package-root <path>] [--installer-js <path>]");
|
|
216
308
|
println!(" naome install-plan [--harness-version <version>]");
|
|
@@ -226,6 +318,18 @@ fn option_value(args: &[String], option: &str) -> Option<String> {
|
|
|
226
318
|
.map(|window| window[1].clone())
|
|
227
319
|
}
|
|
228
320
|
|
|
321
|
+
fn prompt_from_args(args: &[String]) -> Result<String, Box<dyn std::error::Error>> {
|
|
322
|
+
if let Some(prompt_file) = option_value(args, "--prompt-file") {
|
|
323
|
+
return Ok(fs::read_to_string(prompt_file)?);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if let Some(prompt) = option_value(args, "--prompt") {
|
|
327
|
+
return Ok(prompt);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
Err("naome prompt routing requires --prompt-file <path> or --prompt <text>.".into())
|
|
331
|
+
}
|
|
332
|
+
|
|
229
333
|
fn run_install_bridge(command: &str, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
|
|
230
334
|
let package_root = option_value(args, "--package-root")
|
|
231
335
|
.map(PathBuf::from)
|
|
@@ -288,11 +392,7 @@ fn resolve_package_root_from_exe() -> Option<PathBuf> {
|
|
|
288
392
|
let native_dir = platform_dir.parent()?;
|
|
289
393
|
if native_dir.file_name().and_then(|value| value.to_str()) == Some("native") {
|
|
290
394
|
let package_root = native_dir.parent()?.to_path_buf();
|
|
291
|
-
if package_root
|
|
292
|
-
.join("bin")
|
|
293
|
-
.join("naome-node.js")
|
|
294
|
-
.is_file()
|
|
295
|
-
{
|
|
395
|
+
if package_root.join("bin").join("naome-node.js").is_file() {
|
|
296
396
|
return Some(package_root);
|
|
297
397
|
}
|
|
298
398
|
}
|
|
@@ -301,10 +401,7 @@ fn resolve_package_root_from_exe() -> Option<PathBuf> {
|
|
|
301
401
|
|
|
302
402
|
fn resolve_package_root_from_cwd() -> Option<PathBuf> {
|
|
303
403
|
let current = std::env::current_dir().ok()?;
|
|
304
|
-
for candidate in [
|
|
305
|
-
current.join("packages").join("naome"),
|
|
306
|
-
current.clone(),
|
|
307
|
-
] {
|
|
404
|
+
for candidate in [current.join("packages").join("naome"), current.clone()] {
|
|
308
405
|
if candidate.join("bin").join("naome-node.js").is_file() {
|
|
309
406
|
return Some(candidate);
|
|
310
407
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
use std::fs;
|
|
2
1
|
use std::ffi::OsString;
|
|
2
|
+
use std::fs;
|
|
3
3
|
use std::path::Path;
|
|
4
4
|
use std::process::Command;
|
|
5
5
|
|
|
@@ -8,6 +8,7 @@ use serde_json::Value;
|
|
|
8
8
|
use crate::git;
|
|
9
9
|
use crate::models::{CheckDecision, Decision, NaomeError, TaskDecision};
|
|
10
10
|
use crate::paths;
|
|
11
|
+
use crate::task_state::harness_refresh_diff;
|
|
11
12
|
|
|
12
13
|
#[derive(Debug, Clone, Copy)]
|
|
13
14
|
pub struct EvaluationOptions {
|
|
@@ -111,18 +112,38 @@ pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Deci
|
|
|
111
112
|
vec!["create_task"],
|
|
112
113
|
"Task admission is clear; create the next task before feature work.",
|
|
113
114
|
)
|
|
114
|
-
} else {
|
|
115
|
+
} else if has_completed_task_owned_paths(task.as_ref(), &changed_paths) {
|
|
115
116
|
Decision::new(
|
|
116
117
|
"completed_task_unbaselined",
|
|
117
118
|
true,
|
|
118
|
-
"A completed NAOME task
|
|
119
|
+
"A completed NAOME task is verified and waiting for the next routing decision.",
|
|
119
120
|
vec![
|
|
120
121
|
"commit_task_baseline",
|
|
121
122
|
"review_task_diff",
|
|
122
123
|
"request_task_changes",
|
|
123
124
|
"cancel_task_changes",
|
|
124
125
|
],
|
|
125
|
-
"
|
|
126
|
+
"Run NAOME intent for the next natural-language request; deterministic policy can baseline a valid completed task automatically.",
|
|
127
|
+
)
|
|
128
|
+
} else if harness_refresh_diff(root)?.is_some_and(|diff| diff.unrelated_paths.is_empty()) {
|
|
129
|
+
Decision::new(
|
|
130
|
+
"harness_repair_unbaselined",
|
|
131
|
+
true,
|
|
132
|
+
"Machine-owned NAOME harness refresh files changed outside an active task.",
|
|
133
|
+
vec![
|
|
134
|
+
"commit_upgrade_baseline",
|
|
135
|
+
"review_diff_first",
|
|
136
|
+
"cancel_upgrade_baseline",
|
|
137
|
+
],
|
|
138
|
+
"Run NAOME intent for the next natural-language request; deterministic policy can baseline a pure harness refresh automatically.",
|
|
139
|
+
)
|
|
140
|
+
} else {
|
|
141
|
+
Decision::new(
|
|
142
|
+
"dirty_unowned_diff",
|
|
143
|
+
true,
|
|
144
|
+
"The completed NAOME task has been baselined, but unrelated user changes remain.",
|
|
145
|
+
vec!["review_unowned_diff"],
|
|
146
|
+
"Review the unrelated diff, or route a new task so NAOME can isolate task work without touching it.",
|
|
126
147
|
)
|
|
127
148
|
};
|
|
128
149
|
decision.changed_paths = changed_paths;
|
|
@@ -131,6 +152,8 @@ pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Deci
|
|
|
131
152
|
decision.required_context = vec![
|
|
132
153
|
"docs/naome/execution.md".to_string(),
|
|
133
154
|
".naome/task-state.json".to_string(),
|
|
155
|
+
"docs/naome/agent-workflow.md".to_string(),
|
|
156
|
+
"docs/naome/testing.md".to_string(),
|
|
134
157
|
];
|
|
135
158
|
return Ok(decision);
|
|
136
159
|
}
|
|
@@ -175,6 +198,7 @@ pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Deci
|
|
|
175
198
|
decision.required_context = vec![
|
|
176
199
|
"docs/naome/execution.md".to_string(),
|
|
177
200
|
".naome/task-state.json".to_string(),
|
|
201
|
+
"docs/naome/testing.md".to_string(),
|
|
178
202
|
];
|
|
179
203
|
return Ok(decision);
|
|
180
204
|
}
|
|
@@ -226,6 +250,12 @@ pub fn evaluate_decision(root: &Path, options: EvaluationOptions) -> Result<Deci
|
|
|
226
250
|
decision.harness_health = harness_health;
|
|
227
251
|
decision.task_admission = task_admission;
|
|
228
252
|
decision.task = task;
|
|
253
|
+
decision.required_context = vec![
|
|
254
|
+
"docs/naome/agent-workflow.md".to_string(),
|
|
255
|
+
"docs/naome/testing.md".to_string(),
|
|
256
|
+
".naome/verification.json".to_string(),
|
|
257
|
+
"docs/naome/architecture.md".to_string(),
|
|
258
|
+
];
|
|
229
259
|
Ok(decision)
|
|
230
260
|
}
|
|
231
261
|
|
|
@@ -238,15 +268,20 @@ pub fn format_decision(decision: &Decision, mode: &str) -> String {
|
|
|
238
268
|
let mut lines = vec![
|
|
239
269
|
format!("{title}: {}", decision.state),
|
|
240
270
|
format!("Blocked: {}", decision.blocked),
|
|
241
|
-
format!("Summary: {}", decision.
|
|
271
|
+
format!("Summary: {}", decision.user_message),
|
|
242
272
|
format!("Next action: {}", decision.next_action),
|
|
243
273
|
];
|
|
244
274
|
|
|
245
|
-
if
|
|
275
|
+
if should_show_allowed_actions(decision) {
|
|
246
276
|
lines.push(format!(
|
|
247
277
|
"Allowed actions: {}",
|
|
248
278
|
decision.allowed_actions.join(", ")
|
|
249
279
|
));
|
|
280
|
+
} else if is_intent_routed_baseline_state(&decision.state) {
|
|
281
|
+
lines.push(
|
|
282
|
+
"Intent routing: run `naome intent --prompt-file <path>` before presenting human choices."
|
|
283
|
+
.to_string(),
|
|
284
|
+
);
|
|
250
285
|
}
|
|
251
286
|
|
|
252
287
|
if !decision.changed_paths.is_empty() {
|
|
@@ -267,6 +302,19 @@ pub fn format_decision(decision: &Decision, mode: &str) -> String {
|
|
|
267
302
|
lines.join("\n")
|
|
268
303
|
}
|
|
269
304
|
|
|
305
|
+
fn should_show_allowed_actions(decision: &Decision) -> bool {
|
|
306
|
+
!decision.allowed_actions.is_empty() && !is_intent_routed_baseline_state(&decision.state)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
fn is_intent_routed_baseline_state(state: &str) -> bool {
|
|
310
|
+
matches!(
|
|
311
|
+
state,
|
|
312
|
+
"completed_task_unbaselined"
|
|
313
|
+
| "install_or_upgrade_unbaselined"
|
|
314
|
+
| "harness_repair_unbaselined"
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
270
318
|
fn classify_idle_diff(root: &Path, changed_paths: Vec<String>) -> Result<Decision, NaomeError> {
|
|
271
319
|
let manifest = read_json(root, ".naome/manifest.json").unwrap_or(Value::Null);
|
|
272
320
|
let machine_owned = string_array_at(&manifest, "machineOwned");
|
|
@@ -284,7 +332,21 @@ fn classify_idle_diff(root: &Path, changed_paths: Vec<String>) -> Result<Decisio
|
|
|
284
332
|
.iter()
|
|
285
333
|
.all(|path| paths::matches_any(path, &known_harness_paths));
|
|
286
334
|
|
|
287
|
-
let mut decision = if
|
|
335
|
+
let mut decision = if harness_refresh_diff(root)?
|
|
336
|
+
.is_some_and(|diff| diff.unrelated_paths.is_empty())
|
|
337
|
+
{
|
|
338
|
+
Decision::new(
|
|
339
|
+
"harness_repair_unbaselined",
|
|
340
|
+
true,
|
|
341
|
+
"Machine-owned NAOME harness refresh files changed outside an active task.",
|
|
342
|
+
vec![
|
|
343
|
+
"commit_upgrade_baseline",
|
|
344
|
+
"review_diff_first",
|
|
345
|
+
"cancel_upgrade_baseline",
|
|
346
|
+
],
|
|
347
|
+
"Run NAOME intent for the next natural-language request; deterministic policy can baseline a pure harness refresh automatically.",
|
|
348
|
+
)
|
|
349
|
+
} else if all_machine_owned {
|
|
288
350
|
Decision::new(
|
|
289
351
|
"harness_repair_unbaselined",
|
|
290
352
|
true,
|
|
@@ -313,8 +375,8 @@ fn classify_idle_diff(root: &Path, changed_paths: Vec<String>) -> Result<Decisio
|
|
|
313
375
|
"dirty_unowned_diff",
|
|
314
376
|
true,
|
|
315
377
|
"The repository has changes not owned by an active NAOME task.",
|
|
316
|
-
vec!["review_unowned_diff"
|
|
317
|
-
"Review
|
|
378
|
+
vec!["review_unowned_diff"],
|
|
379
|
+
"Review the unowned diff, or route a new task so NAOME can isolate task work without touching it.",
|
|
318
380
|
)
|
|
319
381
|
};
|
|
320
382
|
|
|
@@ -345,6 +407,16 @@ fn is_control_state_path(path: &str) -> bool {
|
|
|
345
407
|
path == ".naome/task-state.json"
|
|
346
408
|
}
|
|
347
409
|
|
|
410
|
+
fn has_completed_task_owned_paths(task: Option<&TaskDecision>, changed_paths: &[String]) -> bool {
|
|
411
|
+
let Some(task) = task else {
|
|
412
|
+
return false;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
changed_paths
|
|
416
|
+
.iter()
|
|
417
|
+
.any(|path| is_control_state_path(path) || paths::matches_any(path, &task.allowed_paths))
|
|
418
|
+
}
|
|
419
|
+
|
|
348
420
|
fn run_node_check(root: &Path, script: &str, args: &[&str]) -> Result<CheckDecision, NaomeError> {
|
|
349
421
|
let mut command_args = vec![script.to_string()];
|
|
350
422
|
command_args.extend(args.iter().map(ToString::to_string));
|
|
@@ -405,7 +477,7 @@ fn string_array_at(value: &Value, key: &str) -> Vec<String> {
|
|
|
405
477
|
}
|
|
406
478
|
|
|
407
479
|
fn extract_known_actions(output: &str) -> Vec<&'static str> {
|
|
408
|
-
const KNOWN: [&str;
|
|
480
|
+
const KNOWN: [&str; 11] = [
|
|
409
481
|
"commit_task_baseline",
|
|
410
482
|
"review_task_diff",
|
|
411
483
|
"request_task_changes",
|
|
@@ -416,7 +488,6 @@ fn extract_known_actions(output: &str) -> Vec<&'static str> {
|
|
|
416
488
|
"run_first_run_protocol",
|
|
417
489
|
"run_upgrade_protocol",
|
|
418
490
|
"review_unowned_diff",
|
|
419
|
-
"clear_or_commit_unowned_diff",
|
|
420
491
|
"create_task",
|
|
421
492
|
];
|
|
422
493
|
|