@lamentis/naome 1.0.1 → 1.1.0
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 +121 -4
- 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 +8 -3
- 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 +1000 -0
- package/crates/naome-core/src/task_state.rs +326 -21
- package/crates/naome-core/tests/decision.rs +8 -6
- package/crates/naome-core/tests/install_plan.rs +12 -3
- package/crates/naome-core/tests/intent.rs +826 -0
- package/crates/naome-core/tests/route.rs +1108 -0
- package/crates/naome-core/tests/task_state.rs +63 -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.0
|
|
79
|
+
version = "1.1.0"
|
|
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.0
|
|
87
|
+
version = "1.1.0"
|
|
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
|
@@ -34,6 +34,7 @@ let machineOwnedPaths = [];
|
|
|
34
34
|
let projectOwnedPaths = [];
|
|
35
35
|
let localOnlyMachineOwnedPaths = [];
|
|
36
36
|
let localOnlyGitIgnoreEntries = [];
|
|
37
|
+
let localOnlyGitExcludeEntries = [];
|
|
37
38
|
let localOnlyGitUntrackPaths = [];
|
|
38
39
|
|
|
39
40
|
const executableMachineOwnedPaths = new Set([
|
|
@@ -57,7 +58,8 @@ Then read .naome/upgrade-state.json, .naome/task-state.json, and docs/naome/exec
|
|
|
57
58
|
Run node .naome/bin/check-harness-health.js before feature work.
|
|
58
59
|
If initialized is false, follow docs/naome/first-run.md.
|
|
59
60
|
Do not begin feature work until NAOME intake is complete.
|
|
60
|
-
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.`;
|
|
61
63
|
|
|
62
64
|
let summaryTitle = "NAOME intake harness installed";
|
|
63
65
|
let nextStepPrompt = firstRunPrompt;
|
|
@@ -363,12 +365,14 @@ function loadInstallPlan() {
|
|
|
363
365
|
assertInstallPlanArray("projectOwned");
|
|
364
366
|
assertInstallPlanArray("localOnlyMachineOwned");
|
|
365
367
|
assertInstallPlanArray("gitignoreEntries");
|
|
368
|
+
assertInstallPlanArray("gitExcludeEntries");
|
|
366
369
|
assertInstallPlanArray("gitUntrackPaths");
|
|
367
370
|
|
|
368
371
|
machineOwnedPaths = installPlan.machineOwned;
|
|
369
372
|
projectOwnedPaths = installPlan.projectOwned;
|
|
370
373
|
localOnlyMachineOwnedPaths = installPlan.localOnlyMachineOwned;
|
|
371
374
|
localOnlyGitIgnoreEntries = installPlan.gitignoreEntries;
|
|
375
|
+
localOnlyGitExcludeEntries = installPlan.gitExcludeEntries;
|
|
372
376
|
localOnlyGitUntrackPaths = installPlan.gitUntrackPaths;
|
|
373
377
|
|
|
374
378
|
return installPlan;
|
|
@@ -521,6 +525,7 @@ function runExistingInstall(existingInstall) {
|
|
|
521
525
|
printError("This repository already uses a newer NAOME harness.");
|
|
522
526
|
console.error(`Installed: v${existingInstall.version}`);
|
|
523
527
|
console.error(`Package: v${packageVersion}`);
|
|
528
|
+
console.error("Run `naome update`, then rerun `naome sync` from the repository root.");
|
|
524
529
|
process.exit(1);
|
|
525
530
|
}
|
|
526
531
|
|
|
@@ -820,11 +825,19 @@ function ensureNaomeIgnore() {
|
|
|
820
825
|
}
|
|
821
826
|
|
|
822
827
|
function ensureLocalOnlySourceControlBoundary() {
|
|
823
|
-
|
|
828
|
+
if (!isInsideGitWorkTree()) {
|
|
829
|
+
ensureLocalOnlyGitIgnoreFallback();
|
|
830
|
+
skipped.push("local git exclude");
|
|
831
|
+
skipped.push("local-only git index cleanup");
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
cleanupLegacyLocalOnlyGitIgnore();
|
|
836
|
+
ensureLocalOnlyGitExclude();
|
|
824
837
|
untrackLocalOnlyGitPaths();
|
|
825
838
|
}
|
|
826
839
|
|
|
827
|
-
function
|
|
840
|
+
function ensureLocalOnlyGitIgnoreFallback() {
|
|
828
841
|
const relativePath = ".gitignore";
|
|
829
842
|
const targetPath = join(targetRoot, relativePath);
|
|
830
843
|
|
|
@@ -841,7 +854,7 @@ function ensureLocalOnlyGitIgnore() {
|
|
|
841
854
|
|
|
842
855
|
const content = existsSync(targetPath) ? readFileSync(targetPath, "utf8") : "";
|
|
843
856
|
const existingLines = new Set(content.split(/\r?\n/).map((line) => line.trim()));
|
|
844
|
-
const missingEntries =
|
|
857
|
+
const missingEntries = legacyLocalOnlyGitIgnoreEntries().filter((entry) => !existingLines.has(entry));
|
|
845
858
|
|
|
846
859
|
if (missingEntries.length === 0) {
|
|
847
860
|
skipped.push(relativePath);
|
|
@@ -857,6 +870,110 @@ function ensureLocalOnlyGitIgnore() {
|
|
|
857
870
|
}
|
|
858
871
|
}
|
|
859
872
|
|
|
873
|
+
function cleanupLegacyLocalOnlyGitIgnore() {
|
|
874
|
+
const relativePath = ".gitignore";
|
|
875
|
+
const targetPath = join(targetRoot, relativePath);
|
|
876
|
+
|
|
877
|
+
if (!existsSync(targetPath)) {
|
|
878
|
+
skipped.push(relativePath);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (hasSymlinkInTargetPath(relativePath)) {
|
|
883
|
+
printError("NAOME cannot clean up .gitignore safely.");
|
|
884
|
+
console.error(".gitignore must be a regular file or must not exist.");
|
|
885
|
+
process.exit(1);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (!lstatSync(targetPath).isFile()) {
|
|
889
|
+
printError("NAOME cannot update .gitignore because that path is not a file.");
|
|
890
|
+
process.exit(1);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
const content = readFileSync(targetPath, "utf8");
|
|
894
|
+
const legacyEntries = new Set(legacyLocalOnlyGitIgnoreEntries());
|
|
895
|
+
const nextContent = cleanBlankLines(content
|
|
896
|
+
.split(/\r?\n/)
|
|
897
|
+
.filter((line) => !legacyEntries.has(line.trim()))
|
|
898
|
+
.join("\n"));
|
|
899
|
+
|
|
900
|
+
if (nextContent === content) {
|
|
901
|
+
skipped.push(relativePath);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
if (nextContent.trim().length === 0) {
|
|
906
|
+
unlinkSync(targetPath);
|
|
907
|
+
} else {
|
|
908
|
+
writeFileSync(targetPath, nextContent);
|
|
909
|
+
}
|
|
910
|
+
updated.push(relativePath);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function ensureLocalOnlyGitExclude() {
|
|
914
|
+
if (!isInsideGitWorkTree()) {
|
|
915
|
+
skipped.push("local git exclude");
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const gitDirResult = spawnSync("git", ["rev-parse", "--absolute-git-dir"], {
|
|
920
|
+
cwd: targetRoot,
|
|
921
|
+
encoding: "utf8",
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
if (gitDirResult.status !== 0 || !gitDirResult.stdout.trim()) {
|
|
925
|
+
skipped.push("local git exclude");
|
|
926
|
+
unsafeSkipped.push("local git exclude");
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
const excludePath = join(gitDirResult.stdout.trim(), "info", "exclude");
|
|
931
|
+
mkdirSync(dirname(excludePath), { recursive: true });
|
|
932
|
+
|
|
933
|
+
if (existsSync(excludePath) && !lstatSync(excludePath).isFile()) {
|
|
934
|
+
printError("NAOME cannot update local git exclude because that path is not a file.");
|
|
935
|
+
process.exit(1);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const content = existsSync(excludePath) ? readFileSync(excludePath, "utf8") : "";
|
|
939
|
+
const existingLines = new Set(content.split(/\r?\n/).map((line) => line.trim()));
|
|
940
|
+
const missingEntries = localOnlyGitExcludeEntries.filter((entry) => !existingLines.has(entry));
|
|
941
|
+
|
|
942
|
+
if (missingEntries.length === 0) {
|
|
943
|
+
skipped.push(".git/info/exclude");
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const nextContent = `${content.trimEnd()}${content.trimEnd() ? "\n\n" : ""}${missingEntries.join("\n")}\n`;
|
|
948
|
+
writeFileSync(excludePath, nextContent);
|
|
949
|
+
updated.push(".git/info/exclude");
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function legacyLocalOnlyGitIgnoreEntries() {
|
|
953
|
+
return [
|
|
954
|
+
"# NAOME local machine-owned harness files.",
|
|
955
|
+
".naome/archive/",
|
|
956
|
+
".naome/bin/naome-rust*",
|
|
957
|
+
...localOnlyMachineOwnedPaths,
|
|
958
|
+
...localOnlyGitIgnoreEntries,
|
|
959
|
+
];
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function cleanBlankLines(content) {
|
|
963
|
+
const lines = content.split(/\r?\n/);
|
|
964
|
+
const cleaned = [];
|
|
965
|
+
|
|
966
|
+
for (const line of lines) {
|
|
967
|
+
if (line.trim() === "" && cleaned.at(-1)?.trim() === "") {
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
cleaned.push(line);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const trimmed = cleaned.join("\n").trimEnd();
|
|
974
|
+
return trimmed ? `${trimmed}\n` : "";
|
|
975
|
+
}
|
|
976
|
+
|
|
860
977
|
function untrackLocalOnlyGitPaths() {
|
|
861
978
|
if (!isInsideGitWorkTree()) {
|
|
862
979
|
skipped.push("local-only git index cleanup");
|
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
|
}
|