@lamentis/naome 1.2.0 → 1.3.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 +108 -47
- package/bin/naome-node.js +2 -1579
- package/bin/naome.js +34 -5
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/dispatcher.rs +7 -2
- package/crates/naome-cli/src/main.rs +37 -22
- package/crates/naome-cli/src/quality_commands.rs +317 -10
- package/crates/naome-cli/src/workflow_commands.rs +21 -1
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/decision/checks.rs +64 -0
- package/crates/naome-core/src/decision/idle.rs +67 -0
- package/crates/naome-core/src/decision/json.rs +36 -0
- package/crates/naome-core/src/decision/states.rs +165 -0
- package/crates/naome-core/src/decision.rs +131 -353
- package/crates/naome-core/src/git.rs +4 -2
- package/crates/naome-core/src/install_plan.rs +4 -0
- package/crates/naome-core/src/lib.rs +12 -6
- package/crates/naome-core/src/paths.rs +3 -1
- package/crates/naome-core/src/quality/adapter_support.rs +89 -0
- package/crates/naome-core/src/quality/adapters.rs +20 -67
- package/crates/naome-core/src/quality/baseline.rs +8 -0
- package/crates/naome-core/src/quality/cache.rs +153 -0
- package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +25 -11
- package/crates/naome-core/src/quality/checks/near_duplicates.rs +4 -2
- package/crates/naome-core/src/quality/checks.rs +7 -8
- package/crates/naome-core/src/quality/cleanup.rs +48 -3
- package/crates/naome-core/src/quality/config.rs +8 -15
- package/crates/naome-core/src/quality/config_support.rs +24 -0
- package/crates/naome-core/src/quality/mod.rs +72 -6
- package/crates/naome-core/src/quality/scanner/analysis/normalize.rs +78 -0
- package/crates/naome-core/src/quality/scanner/analysis.rs +160 -0
- package/crates/naome-core/src/quality/scanner/repo_paths.rs +39 -3
- package/crates/naome-core/src/quality/scanner.rs +200 -215
- package/crates/naome-core/src/quality/semantic/checks.rs +134 -0
- package/crates/naome-core/src/quality/semantic/extract.rs +158 -0
- package/crates/naome-core/src/quality/semantic/model.rs +85 -0
- package/crates/naome-core/src/quality/semantic/route.rs +52 -0
- package/crates/naome-core/src/quality/semantic.rs +68 -0
- package/crates/naome-core/src/quality/structure/adapters.rs +84 -0
- package/crates/naome-core/src/quality/structure/checks/basic.rs +153 -0
- package/crates/naome-core/src/quality/structure/checks/directory.rs +134 -0
- package/crates/naome-core/src/quality/structure/checks/pairing.rs +63 -0
- package/crates/naome-core/src/quality/structure/checks.rs +124 -0
- package/crates/naome-core/src/quality/structure/classify/roles.rs +188 -0
- package/crates/naome-core/src/quality/structure/classify.rs +146 -0
- package/crates/naome-core/src/quality/structure/config.rs +89 -0
- package/crates/naome-core/src/quality/structure/defaults.rs +107 -0
- package/crates/naome-core/src/quality/structure/mod.rs +77 -0
- package/crates/naome-core/src/quality/structure/model.rs +131 -0
- package/crates/naome-core/src/quality/types.rs +43 -2
- package/crates/naome-core/src/route/builtin_checks.rs +141 -0
- package/crates/naome-core/src/route/builtin_context.rs +73 -0
- package/crates/naome-core/src/route/builtin_integrity.rs +49 -0
- package/crates/naome-core/src/route/builtin_require.rs +40 -0
- package/crates/naome-core/src/route/context.rs +180 -0
- package/crates/naome-core/src/route/execution.rs +96 -0
- package/crates/naome-core/src/route/execution_baselines.rs +146 -0
- package/crates/naome-core/src/route/execution_support.rs +57 -0
- package/crates/naome-core/src/route/execution_tasks.rs +71 -0
- package/crates/naome-core/src/route/git_ops.rs +72 -0
- package/crates/naome-core/src/route/quality_gate.rs +73 -0
- package/crates/naome-core/src/route/quality_gate_config.rs +126 -0
- package/crates/naome-core/src/route/quality_gate_snapshot.rs +69 -0
- package/crates/naome-core/src/route/worktree.rs +75 -0
- package/crates/naome-core/src/route/worktree_files.rs +32 -0
- package/crates/naome-core/src/route/worktree_plan.rs +131 -0
- package/crates/naome-core/src/route.rs +44 -1217
- package/crates/naome-core/src/verification.rs +1 -0
- package/crates/naome-core/src/workflow/doctor.rs +144 -0
- package/crates/naome-core/src/workflow/mod.rs +2 -0
- package/crates/naome-core/src/workflow/mutation.rs +1 -2
- package/crates/naome-core/tests/decision.rs +24 -118
- package/crates/naome-core/tests/harness_health.rs +2 -0
- package/crates/naome-core/tests/install_plan.rs +2 -0
- package/crates/naome-core/tests/quality.rs +26 -123
- package/crates/naome-core/tests/quality_performance.rs +231 -0
- package/crates/naome-core/tests/quality_structure.rs +116 -0
- package/crates/naome-core/tests/quality_structure_adapters.rs +98 -0
- package/crates/naome-core/tests/quality_structure_policy.rs +144 -0
- package/crates/naome-core/tests/quality_structure_support/mod.rs +249 -0
- package/crates/naome-core/tests/repo_support/mod.rs +16 -0
- package/crates/naome-core/tests/repo_support/repo.rs +113 -0
- package/crates/naome-core/tests/repo_support/repo_factories.rs +99 -0
- package/crates/naome-core/tests/repo_support/repo_helpers.rs +123 -0
- package/crates/naome-core/tests/repo_support/routes.rs +81 -0
- package/crates/naome-core/tests/repo_support/verification.rs +168 -0
- package/crates/naome-core/tests/repo_support/verification_values.rs +135 -0
- package/crates/naome-core/tests/route.rs +1 -1376
- package/crates/naome-core/tests/route_baseline.rs +86 -0
- package/crates/naome-core/tests/route_completion.rs +141 -0
- package/crates/naome-core/tests/route_harness_refresh.rs +135 -0
- package/crates/naome-core/tests/route_user_diff.rs +202 -0
- package/crates/naome-core/tests/route_worktree.rs +54 -0
- package/crates/naome-core/tests/semantic_legacy.rs +140 -0
- package/crates/naome-core/tests/task_state.rs +60 -432
- package/crates/naome-core/tests/task_state_compact_support/repo.rs +1 -1
- package/crates/naome-core/tests/task_state_support/mod.rs +163 -0
- package/crates/naome-core/tests/task_state_support/states.rs +84 -0
- package/crates/naome-core/tests/verification.rs +4 -45
- package/crates/naome-core/tests/verification_contract.rs +22 -78
- package/crates/naome-core/tests/workflow_doctor.rs +24 -0
- package/crates/naome-core/tests/workflow_policy.rs +6 -1
- package/crates/naome-core/tests/workflow_support/mod.rs +1 -1
- package/installer/agents.js +90 -0
- package/installer/context.js +67 -0
- package/installer/filesystem.js +166 -0
- package/installer/flows.js +84 -0
- package/installer/git-boundary.js +171 -0
- package/installer/git-hook-content.js +36 -0
- package/installer/git-hooks.js +134 -0
- package/installer/git-local.js +2 -0
- package/installer/git-shared.js +35 -0
- package/installer/harness-file-ops.js +140 -0
- package/installer/harness-files.js +56 -0
- package/installer/harness-verification.js +123 -0
- package/installer/install-plan.js +66 -0
- package/installer/main.js +25 -0
- package/installer/manifest-state.js +167 -0
- package/installer/native-build.js +24 -0
- package/installer/native-format.js +6 -0
- package/installer/native.js +162 -0
- package/installer/output.js +131 -0
- package/installer/version.js +32 -0
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +2 -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 +32 -21
- package/templates/naome-root/.naome/manifest.json +5 -3
- package/templates/naome-root/.naome/repository-structure.json +90 -0
- package/templates/naome-root/.naome/verification.json +1 -0
- package/templates/naome-root/.naomeignore +1 -0
- package/templates/naome-root/docs/naome/agent-workflow.md +16 -14
- package/templates/naome-root/docs/naome/index.md +4 -3
- package/templates/naome-root/docs/naome/repository-quality.md +66 -4
- package/templates/naome-root/docs/naome/repository-structure.md +51 -0
- package/templates/naome-root/docs/naome/testing.md +2 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
copyFileSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
lstatSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
unlinkSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { dirname, join, relative } from "node:path";
|
|
11
|
+
|
|
12
|
+
import { archiveUpgradePath, hasSymlinkInTargetPath } from "./filesystem.js";
|
|
13
|
+
import { hasGeneratedIntegrity, machineFileHash } from "./native.js";
|
|
14
|
+
import { printError } from "./output.js";
|
|
15
|
+
|
|
16
|
+
export function ensureTemplateFile(ctx, relativePath) {
|
|
17
|
+
const sourcePath = join(ctx.templateRoot, relativePath);
|
|
18
|
+
const targetPath = join(ctx.targetRoot, relativePath);
|
|
19
|
+
|
|
20
|
+
if (hasSymlinkInTargetPath(ctx, relativePath)) {
|
|
21
|
+
printError(ctx, `NAOME cannot write ${relativePath} safely.`);
|
|
22
|
+
console.error(`${relativePath} must be a regular file or must not exist.`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (existsSync(targetPath)) {
|
|
27
|
+
if (!lstatSync(targetPath).isFile()) {
|
|
28
|
+
printError(ctx, `NAOME cannot write ${relativePath} because that path is not a file.`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ctx.skipped.push(relativePath);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
37
|
+
copyFileSync(sourcePath, targetPath);
|
|
38
|
+
ctx.installed.push(relativePath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function removeBranchControlledHookShim(ctx, relativePath, archiveDirName) {
|
|
42
|
+
const targetPath = join(ctx.targetRoot, relativePath);
|
|
43
|
+
if (!existsSync(targetPath)) {
|
|
44
|
+
ctx.skipped.push(relativePath);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (hasSymlinkInTargetPath(ctx, relativePath) || !lstatSync(targetPath).isFile()) {
|
|
49
|
+
ctx.skipped.push(relativePath);
|
|
50
|
+
ctx.unsafeSkipped.push(relativePath);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const archivePath = archiveUpgradePath(ctx, archiveDirName, relativePath);
|
|
55
|
+
mkdirSync(dirname(archivePath), { recursive: true });
|
|
56
|
+
copyFileSync(targetPath, archivePath);
|
|
57
|
+
unlinkSync(targetPath);
|
|
58
|
+
ctx.updated.push(relativePath);
|
|
59
|
+
ctx.archived.push({ from: relativePath, to: relative(ctx.targetRoot, archivePath) });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function replaceHarnessFile(ctx, relativePath, archiveDirName) {
|
|
63
|
+
const sourcePath = join(ctx.templateRoot, relativePath);
|
|
64
|
+
const targetPath = join(ctx.targetRoot, relativePath);
|
|
65
|
+
|
|
66
|
+
if (hasSymlinkInTargetPath(ctx, relativePath)) {
|
|
67
|
+
printError(ctx, `NAOME cannot update ${relativePath} safely.`);
|
|
68
|
+
console.error(`${relativePath} must be a regular file or must not exist.`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!existsSync(targetPath)) {
|
|
73
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
74
|
+
copyFileSync(sourcePath, targetPath);
|
|
75
|
+
ctx.installed.push(relativePath);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!lstatSync(targetPath).isFile()) {
|
|
80
|
+
printError(ctx, `NAOME cannot update ${relativePath} because that path is not a file.`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
replaceChangedHarnessFile(ctx, relativePath, archiveDirName, sourcePath, targetPath);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function ensureNaomeIgnore(ctx) {
|
|
88
|
+
const relativePath = ".naomeignore";
|
|
89
|
+
const sourcePath = join(ctx.templateRoot, relativePath);
|
|
90
|
+
const targetPath = join(ctx.targetRoot, relativePath);
|
|
91
|
+
|
|
92
|
+
if (hasSymlinkInTargetPath(ctx, relativePath)) {
|
|
93
|
+
printError(ctx, "NAOME cannot write .naomeignore safely.");
|
|
94
|
+
console.error(".naomeignore must be a regular file or must not exist.");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!existsSync(targetPath)) {
|
|
99
|
+
copyFileSync(sourcePath, targetPath);
|
|
100
|
+
ctx.installed.push(relativePath);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!lstatSync(targetPath).isFile()) {
|
|
105
|
+
printError(ctx, "NAOME cannot update .naomeignore because that path is not a file.");
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const content = readFileSync(targetPath, "utf8");
|
|
110
|
+
if (content.split(/\r?\n/).some((line) => line.trim() === ".naome/archive/")) {
|
|
111
|
+
ctx.skipped.push(relativePath);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
writeFileSync(targetPath, `${content.trimEnd()}\n\n.naome/archive/\n`);
|
|
116
|
+
ctx.updated.push(relativePath);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function replaceChangedHarnessFile(ctx, relativePath, archiveDirName, sourcePath, targetPath) {
|
|
120
|
+
const nextContent = readFileSync(sourcePath);
|
|
121
|
+
const currentContent = readFileSync(targetPath);
|
|
122
|
+
if (currentContent.equals(nextContent) || unchangedMachineHash(ctx, relativePath, currentContent, nextContent)) {
|
|
123
|
+
ctx.skipped.push(relativePath);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const archivePath = archiveUpgradePath(ctx, archiveDirName, relativePath);
|
|
128
|
+
mkdirSync(dirname(archivePath), { recursive: true });
|
|
129
|
+
copyFileSync(targetPath, archivePath);
|
|
130
|
+
writeFileSync(targetPath, nextContent);
|
|
131
|
+
ctx.updated.push(relativePath);
|
|
132
|
+
ctx.archived.push({ from: relativePath, to: relative(ctx.targetRoot, archivePath) });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function unchangedMachineHash(ctx, relativePath, currentContent, nextContent) {
|
|
136
|
+
return (
|
|
137
|
+
!hasGeneratedIntegrity(ctx, relativePath) &&
|
|
138
|
+
machineFileHash(ctx, relativePath, currentContent) === machineFileHash(ctx, relativePath, nextContent)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureNaomeIgnore,
|
|
3
|
+
ensureTemplateFile,
|
|
4
|
+
removeBranchControlledHookShim,
|
|
5
|
+
replaceHarnessFile,
|
|
6
|
+
} from "./harness-file-ops.js";
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
ensureBuiltInVerificationChecks,
|
|
10
|
+
ensureTestingProofHarnessSections,
|
|
11
|
+
} from "./harness-verification.js";
|
|
12
|
+
|
|
13
|
+
export function ensureCoreHarnessFiles(ctx, archiveDirName) {
|
|
14
|
+
ensureNaomeIgnore(ctx);
|
|
15
|
+
ensureTemplateFile(ctx, ".naome/verification.json");
|
|
16
|
+
replaceHarnessFile(ctx, "AGENTS.md", archiveDirName);
|
|
17
|
+
replaceHarnessFile(ctx, "docs/naome/index.md", archiveDirName);
|
|
18
|
+
replaceHarnessFile(ctx, "docs/naome/first-run.md", archiveDirName);
|
|
19
|
+
replaceHarnessFile(ctx, "docs/naome/agent-workflow.md", archiveDirName);
|
|
20
|
+
ensureTemplateFile(ctx, "docs/naome/security.md");
|
|
21
|
+
replaceHarnessFile(ctx, "docs/naome/upgrade.md", archiveDirName);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ensureRepositoryStructurePolicyFiles(ctx) {
|
|
25
|
+
ensureTemplateFile(ctx, ".naome/repository-structure.json");
|
|
26
|
+
ensureTemplateFile(ctx, "docs/naome/repository-structure.md");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function ensureTaskControlHarnessFiles(ctx, archiveDirName) {
|
|
30
|
+
ensureTemplateFile(ctx, ".naome/task-state.json");
|
|
31
|
+
replaceHarnessFile(ctx, ".naome/task-contract.schema.json", archiveDirName);
|
|
32
|
+
replaceHarnessFile(ctx, ".naome/bin/check-task-state.js", archiveDirName);
|
|
33
|
+
replaceHarnessFile(ctx, "AGENTS.md", archiveDirName);
|
|
34
|
+
replaceHarnessFile(ctx, "docs/naome/index.md", archiveDirName);
|
|
35
|
+
replaceHarnessFile(ctx, "docs/naome/agent-workflow.md", archiveDirName);
|
|
36
|
+
replaceHarnessFile(ctx, "docs/naome/execution.md", archiveDirName);
|
|
37
|
+
replaceHarnessFile(ctx, "docs/naome/upgrade.md", archiveDirName);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function ensureHarnessHealthFiles(ctx, archiveDirName) {
|
|
41
|
+
replaceHarnessFile(ctx, "AGENTS.md", archiveDirName);
|
|
42
|
+
replaceHarnessFile(ctx, ".naome/package.json", archiveDirName);
|
|
43
|
+
replaceHarnessFile(ctx, ".naome/bin/naome.js", archiveDirName);
|
|
44
|
+
replaceHarnessFile(ctx, ".naome/bin/check-task-state.js", archiveDirName);
|
|
45
|
+
replaceHarnessFile(ctx, ".naome/bin/check-harness-health.js", archiveDirName);
|
|
46
|
+
removeBranchControlledHookShim(ctx, ".naome/git-hooks/pre-commit", archiveDirName);
|
|
47
|
+
removeBranchControlledHookShim(ctx, ".naome/git-hooks/pre-push", archiveDirName);
|
|
48
|
+
replaceHarnessFile(ctx, ".naome/task-contract.schema.json", archiveDirName);
|
|
49
|
+
replaceHarnessFile(ctx, "docs/naome/index.md", archiveDirName);
|
|
50
|
+
replaceHarnessFile(ctx, "docs/naome/first-run.md", archiveDirName);
|
|
51
|
+
replaceHarnessFile(ctx, "docs/naome/agent-workflow.md", archiveDirName);
|
|
52
|
+
replaceHarnessFile(ctx, "docs/naome/execution.md", archiveDirName);
|
|
53
|
+
ensureTemplateFile(ctx, "docs/naome/security.md");
|
|
54
|
+
replaceHarnessFile(ctx, "docs/naome/upgrade.md", archiveDirName);
|
|
55
|
+
ensureNaomeIgnore(ctx);
|
|
56
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { hasSymlinkInTargetPath, removeSkipped } from "./filesystem.js";
|
|
6
|
+
import { findNativeDecisionBinary, usesSourceNativeFallback } from "./native.js";
|
|
7
|
+
import { printCommandFailure, printError } from "./output.js";
|
|
8
|
+
|
|
9
|
+
export function ensureBuiltInVerificationChecks(ctx) {
|
|
10
|
+
const relativePath = ".naome/verification.json";
|
|
11
|
+
const targetPath = join(ctx.targetRoot, relativePath);
|
|
12
|
+
|
|
13
|
+
if (hasSymlinkInTargetPath(ctx, relativePath)) {
|
|
14
|
+
printError(ctx, "NAOME cannot update .naome/verification.json safely.");
|
|
15
|
+
console.error(".naome/verification.json must be a regular file or must not exist.");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
ensureVerificationFileExists(ctx, relativePath, targetPath);
|
|
20
|
+
const nativePath = verificationSeederBinary(ctx);
|
|
21
|
+
const result = spawnSync(nativePath, ["seed-verification"], {
|
|
22
|
+
cwd: ctx.targetRoot,
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (result.status !== 0) {
|
|
27
|
+
printCommandFailure(ctx, "NAOME could not seed built-in verification checks.", result);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!result.stdout.includes("updated")) {
|
|
31
|
+
ctx.skipped.push(relativePath);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
removeSkipped(ctx, relativePath);
|
|
36
|
+
ctx.updated.push(relativePath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function ensureTestingProofHarnessSections(ctx) {
|
|
40
|
+
const relativePath = "docs/naome/testing.md";
|
|
41
|
+
const targetPath = join(ctx.targetRoot, relativePath);
|
|
42
|
+
ensureWritableTestingDoc(ctx, relativePath, targetPath);
|
|
43
|
+
|
|
44
|
+
let content = readFileSync(targetPath, "utf8");
|
|
45
|
+
const originalContent = content;
|
|
46
|
+
for (const [heading, body] of testingSections()) {
|
|
47
|
+
if (!hasMarkdownHeading(content, heading)) {
|
|
48
|
+
content = `${content.trimEnd()}\n\n${heading}\n${body}`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (content !== originalContent) {
|
|
53
|
+
writeFileSync(targetPath, content.endsWith("\n") ? content : `${content}\n`);
|
|
54
|
+
ctx.updated.push(relativePath);
|
|
55
|
+
} else {
|
|
56
|
+
ctx.skipped.push(relativePath);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ensureVerificationFileExists(ctx, relativePath, targetPath) {
|
|
61
|
+
if (!existsSync(targetPath)) {
|
|
62
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
63
|
+
copyFileSync(join(ctx.templateRoot, relativePath), targetPath);
|
|
64
|
+
ctx.installed.push(relativePath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!lstatSync(targetPath).isFile()) {
|
|
68
|
+
printError(ctx, "NAOME cannot update .naome/verification.json because that path is not a file.");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function verificationSeederBinary(ctx) {
|
|
74
|
+
const nativePath = usesSourceNativeFallback(ctx)
|
|
75
|
+
? findNativeDecisionBinary(ctx)
|
|
76
|
+
: join(ctx.targetRoot, ctx.nativeBinaryRelativePath);
|
|
77
|
+
if (
|
|
78
|
+
!nativePath ||
|
|
79
|
+
!existsSync(nativePath) ||
|
|
80
|
+
(!usesSourceNativeFallback(ctx) && hasSymlinkInTargetPath(ctx, ctx.nativeBinaryRelativePath)) ||
|
|
81
|
+
!lstatSync(nativePath).isFile()
|
|
82
|
+
) {
|
|
83
|
+
printError(ctx, "NAOME native decision binary is unavailable for verification seeding.");
|
|
84
|
+
console.error(`Expected ${ctx.nativeBinaryRelativePath} to be installed before seeding verification checks.`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
return nativePath;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function ensureWritableTestingDoc(ctx, relativePath, targetPath) {
|
|
91
|
+
if (hasSymlinkInTargetPath(ctx, relativePath)) {
|
|
92
|
+
printError(ctx, "NAOME cannot update docs/naome/testing.md safely.");
|
|
93
|
+
console.error("docs/naome/testing.md must be a regular file or must not exist.");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!existsSync(targetPath)) {
|
|
98
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
99
|
+
copyFileSync(join(ctx.templateRoot, relativePath), targetPath);
|
|
100
|
+
ctx.installed.push(relativePath);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!lstatSync(targetPath).isFile()) {
|
|
105
|
+
printError(ctx, "NAOME cannot update docs/naome/testing.md because that path is not a file.");
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function testingSections() {
|
|
111
|
+
return [
|
|
112
|
+
["## Verification Map", "\n| Change type | Required proof | Command | Notes |\n|---|---|---|---|\n| Unknown | Unknown | Unknown | Fill during NAOME upgrade. |\n"],
|
|
113
|
+
["## Known Checks", "\n| Check id | Command | Cwd | Cost | Last verified |\n|---|---|---|---|---|\n| Unknown | Unknown | Unknown | Unknown | Unknown |\n"],
|
|
114
|
+
["## Change Type Rules", "\n| Change type | Paths | Required checks |\n|---|---|---|\n| Unknown | Unknown | Unknown |\n"],
|
|
115
|
+
["## Release Gates", "\n| Check id | Required when |\n|---|---|\n| Unknown | Before release, when applicable. |\n"],
|
|
116
|
+
["## Rules", "\n- Mirror durable entries in `.naome/verification.json`.\n- Use only commands proven by repository files, CI, or user confirmation.\n- Report exact commands and results. Do not claim proof that did not run.\n"],
|
|
117
|
+
["## Evidence", "\n- Unknown.\n"],
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function hasMarkdownHeading(content, heading) {
|
|
122
|
+
return content.split(/\r?\n/).some((line) => line.trim() === heading);
|
|
123
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
import { findNativeDecisionBinary } from "./native.js";
|
|
4
|
+
import { printCommandFailure, printError } from "./output.js";
|
|
5
|
+
|
|
6
|
+
export function loadInstallPlan(ctx) {
|
|
7
|
+
if (ctx.installPlan) {
|
|
8
|
+
return ctx.installPlan;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const nativePath = findNativeDecisionBinary(ctx);
|
|
12
|
+
if (!nativePath) {
|
|
13
|
+
printError(ctx, "NAOME native installer policy is unavailable.");
|
|
14
|
+
console.error("Install Cargo or provide NAOME_NATIVE_BIN with a built naome binary, then rerun naome sync.");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = spawnSync(nativePath, ["install-plan", "--harness-version", ctx.packageVersion], {
|
|
19
|
+
cwd: ctx.targetRoot,
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (result.status !== 0) {
|
|
24
|
+
printCommandFailure(ctx, "NAOME could not load its Rust install plan.", result);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
assignInstallPlan(ctx, parseInstallPlan(ctx, result.stdout));
|
|
28
|
+
return ctx.installPlan;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseInstallPlan(ctx, output) {
|
|
32
|
+
try {
|
|
33
|
+
const installPlan = JSON.parse(output);
|
|
34
|
+
assertInstallPlanArray(ctx, installPlan, "machineOwned");
|
|
35
|
+
assertInstallPlanArray(ctx, installPlan, "projectOwned");
|
|
36
|
+
assertInstallPlanArray(ctx, installPlan, "localOnlyMachineOwned");
|
|
37
|
+
assertInstallPlanArray(ctx, installPlan, "gitignoreEntries");
|
|
38
|
+
assertInstallPlanArray(ctx, installPlan, "gitExcludeEntries");
|
|
39
|
+
assertInstallPlanArray(ctx, installPlan, "gitUntrackPaths");
|
|
40
|
+
return installPlan;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
printError(ctx, "NAOME Rust install plan is not valid JSON.");
|
|
43
|
+
console.error(error.message);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function assertInstallPlanArray(ctx, installPlan, key) {
|
|
49
|
+
const invalid =
|
|
50
|
+
!Array.isArray(installPlan?.[key]) ||
|
|
51
|
+
installPlan[key].some((entry) => typeof entry !== "string" || entry.length === 0);
|
|
52
|
+
if (invalid) {
|
|
53
|
+
printError(ctx, `NAOME Rust install plan has invalid ${key}.`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function assignInstallPlan(ctx, installPlan) {
|
|
59
|
+
ctx.installPlan = installPlan;
|
|
60
|
+
ctx.machineOwnedPaths = installPlan.machineOwned;
|
|
61
|
+
ctx.projectOwnedPaths = installPlan.projectOwned;
|
|
62
|
+
ctx.localOnlyMachineOwnedPaths = installPlan.localOnlyMachineOwned;
|
|
63
|
+
ctx.localOnlyGitIgnoreEntries = installPlan.gitignoreEntries;
|
|
64
|
+
ctx.localOnlyGitExcludeEntries = installPlan.gitExcludeEntries;
|
|
65
|
+
ctx.localOnlyGitUntrackPaths = installPlan.gitUntrackPaths;
|
|
66
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createInstallerContext } from "./context.js";
|
|
2
|
+
import { assertTemplateRoot } from "./filesystem.js";
|
|
3
|
+
import { configureGitHooks, ensureExecutableMachineOwnedFiles } from "./git-local.js";
|
|
4
|
+
import { loadInstallPlan } from "./install-plan.js";
|
|
5
|
+
import { readExistingInstall } from "./manifest-state.js";
|
|
6
|
+
import { printHeader, printSummary } from "./output.js";
|
|
7
|
+
import { runExistingInstall, runFreshInstall } from "./flows.js";
|
|
8
|
+
|
|
9
|
+
export async function runNaomeNodeCli() {
|
|
10
|
+
const ctx = createInstallerContext();
|
|
11
|
+
assertTemplateRoot(ctx);
|
|
12
|
+
loadInstallPlan(ctx);
|
|
13
|
+
|
|
14
|
+
printHeader(ctx);
|
|
15
|
+
const existingInstall = readExistingInstall(ctx);
|
|
16
|
+
if (existingInstall) {
|
|
17
|
+
runExistingInstall(ctx, existingInstall);
|
|
18
|
+
} else {
|
|
19
|
+
await runFreshInstall(ctx);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
ensureExecutableMachineOwnedFiles(ctx);
|
|
23
|
+
configureGitHooks(ctx);
|
|
24
|
+
printSummary(ctx, existingInstall);
|
|
25
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { hasSymlinkInTargetPath } from "./filesystem.js";
|
|
5
|
+
import { installedNativeBinaryHash, templateIntegrity, usesSourceNativeFallback } from "./native.js";
|
|
6
|
+
import { printError } from "./output.js";
|
|
7
|
+
import { isVersion } from "./version.js";
|
|
8
|
+
|
|
9
|
+
export function readExistingInstall(ctx) {
|
|
10
|
+
const manifestPath = join(ctx.targetRoot, ".naome", "manifest.json");
|
|
11
|
+
if (!existsSync(manifestPath)) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (hasSymlinkInTargetPath(ctx, ".naome/manifest.json")) {
|
|
16
|
+
printError(ctx, "NAOME cannot read .naome/manifest.json safely.");
|
|
17
|
+
console.error(".naome/manifest.json must be a regular file.");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const manifest = readJsonFile(ctx, manifestPath, "NAOME manifest is not valid JSON.");
|
|
22
|
+
if (!isVersion(manifest.harnessVersion)) {
|
|
23
|
+
printError(ctx, "NAOME manifest has no valid harnessVersion.");
|
|
24
|
+
console.error(".naome/manifest.json must contain a semver harnessVersion.");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { manifest, version: manifest.harnessVersion };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function readUpgradeState(ctx) {
|
|
32
|
+
const relativePath = ".naome/upgrade-state.json";
|
|
33
|
+
const targetPath = join(ctx.targetRoot, relativePath);
|
|
34
|
+
|
|
35
|
+
if (!existsSync(targetPath)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (hasSymlinkInTargetPath(ctx, relativePath)) {
|
|
40
|
+
printError(ctx, "NAOME cannot read .naome/upgrade-state.json safely.");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return readJsonFile(ctx, targetPath, "NAOME upgrade state is not valid JSON.");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function patchManifestDate(ctx) {
|
|
48
|
+
const relativePath = ".naome/manifest.json";
|
|
49
|
+
const manifestPath = join(ctx.targetRoot, relativePath);
|
|
50
|
+
if (
|
|
51
|
+
!existsSync(manifestPath) ||
|
|
52
|
+
ctx.skipped.includes(relativePath) ||
|
|
53
|
+
hasSymlinkInTargetPath(ctx, relativePath)
|
|
54
|
+
) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
59
|
+
applyManifestHealthMetadata(ctx, manifest);
|
|
60
|
+
manifest.installedAt = new Date().toISOString();
|
|
61
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function refreshManifestHealthMetadata(ctx) {
|
|
65
|
+
const relativePath = ".naome/manifest.json";
|
|
66
|
+
const targetPath = join(ctx.targetRoot, relativePath);
|
|
67
|
+
|
|
68
|
+
if (hasSymlinkInTargetPath(ctx, relativePath)) {
|
|
69
|
+
printError(ctx, "NAOME cannot write .naome/manifest.json safely.");
|
|
70
|
+
console.error(".naome/manifest.json must be a regular file.");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!existsSync(targetPath) || !lstatSync(targetPath).isFile()) {
|
|
75
|
+
printError(ctx, "NAOME cannot write .naome/manifest.json because that path is not a file.");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const manifest = JSON.parse(readFileSync(targetPath, "utf8"));
|
|
80
|
+
const originalContent = JSON.stringify(manifest, null, 2);
|
|
81
|
+
applyManifestHealthMetadata(ctx, manifest);
|
|
82
|
+
const nextContent = JSON.stringify(manifest, null, 2);
|
|
83
|
+
|
|
84
|
+
if (nextContent === originalContent) {
|
|
85
|
+
ctx.skipped.push(relativePath);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
writeFileSync(targetPath, `${nextContent}\n`);
|
|
90
|
+
ctx.updated.push(relativePath);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function ensureCompleteUpgradeState(ctx, fromVersion = null) {
|
|
94
|
+
const existingUpgradeState = readUpgradeState(ctx);
|
|
95
|
+
if (
|
|
96
|
+
existingUpgradeState?.status === "complete" &&
|
|
97
|
+
existingUpgradeState?.toVersion === ctx.packageVersion &&
|
|
98
|
+
(fromVersion === null || existingUpgradeState?.fromVersion === fromVersion) &&
|
|
99
|
+
Array.isArray(existingUpgradeState.pending) &&
|
|
100
|
+
existingUpgradeState.pending.length === 0 &&
|
|
101
|
+
Array.isArray(existingUpgradeState.completed)
|
|
102
|
+
) {
|
|
103
|
+
ctx.skipped.push(".naome/upgrade-state.json");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
writeUpgradeState(ctx, {
|
|
108
|
+
status: "complete",
|
|
109
|
+
fromVersion,
|
|
110
|
+
toVersion: ctx.packageVersion,
|
|
111
|
+
pending: [],
|
|
112
|
+
completed: [],
|
|
113
|
+
updatedAt: new Date().toISOString(),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function applyManifestHealthMetadata(ctx, manifest) {
|
|
118
|
+
manifest.harnessVersion = ctx.packageVersion;
|
|
119
|
+
manifest.machineOwned = [...ctx.machineOwnedPaths];
|
|
120
|
+
manifest.projectOwned = ctx.projectOwnedPaths;
|
|
121
|
+
manifest.integrity = templateIntegrity(ctx);
|
|
122
|
+
|
|
123
|
+
const nativeHash = installedNativeBinaryHash(ctx);
|
|
124
|
+
if (!usesSourceNativeFallback(ctx) && nativeHash) {
|
|
125
|
+
if (!manifest.machineOwned.includes(ctx.nativeBinaryRelativePath)) {
|
|
126
|
+
manifest.machineOwned.push(ctx.nativeBinaryRelativePath);
|
|
127
|
+
}
|
|
128
|
+
manifest.integrity[ctx.nativeBinaryRelativePath] = `sha256:${nativeHash}`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function writeUpgradeState(ctx, state) {
|
|
133
|
+
const relativePath = ".naome/upgrade-state.json";
|
|
134
|
+
const targetPath = join(ctx.targetRoot, relativePath);
|
|
135
|
+
assertWritableUpgradeStatePath(ctx);
|
|
136
|
+
|
|
137
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
138
|
+
writeFileSync(targetPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
139
|
+
ctx.updated.push(relativePath);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function assertWritableUpgradeStatePath(ctx) {
|
|
143
|
+
const relativePath = ".naome/upgrade-state.json";
|
|
144
|
+
const targetPath = join(ctx.targetRoot, relativePath);
|
|
145
|
+
|
|
146
|
+
if (hasSymlinkInTargetPath(ctx, relativePath)) {
|
|
147
|
+
printError(ctx, "NAOME cannot write .naome/upgrade-state.json safely.");
|
|
148
|
+
console.error(".naome/upgrade-state.json must be a regular file or must not exist.");
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (existsSync(targetPath) && !lstatSync(targetPath).isFile()) {
|
|
153
|
+
printError(ctx, "NAOME cannot write .naome/upgrade-state.json because that path is not a file.");
|
|
154
|
+
console.error(".naome/upgrade-state.json must be a regular file or must not exist.");
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function readJsonFile(ctx, path, message) {
|
|
160
|
+
try {
|
|
161
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
162
|
+
} catch (error) {
|
|
163
|
+
printError(ctx, message);
|
|
164
|
+
console.error(error.message);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, lstatSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export function buildNativeDecisionBinary(ctx) {
|
|
6
|
+
const manifestPath = join(ctx.packageRoot, "Cargo.toml");
|
|
7
|
+
if (!existsSync(manifestPath)) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const result = spawnSync(
|
|
12
|
+
"cargo",
|
|
13
|
+
["build", "--release", "--manifest-path", manifestPath, "-p", "naome-cli"],
|
|
14
|
+
{ cwd: ctx.packageRoot, encoding: "utf8" },
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
if (result.status !== 0) {
|
|
18
|
+
ctx.skipped.push("native decision binary build");
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const builtPath = join(ctx.packageRoot, "target", "release", ctx.nativeBinaryName);
|
|
23
|
+
return existsSync(builtPath) && lstatSync(builtPath).isFile() ? builtPath : null;
|
|
24
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function formatExpectedIntegrityBlock(integrity) {
|
|
2
|
+
const entries = Object.entries(integrity)
|
|
3
|
+
.map(([relativePath, expectedHash]) => ` ${JSON.stringify(relativePath)}: ${JSON.stringify(expectedHash)}`)
|
|
4
|
+
.join(",\n");
|
|
5
|
+
return `const expectedMachineOwnedIntegrity = Object.freeze({\n${entries}\n});\n`;
|
|
6
|
+
}
|