@lamentis/naome 1.2.1 → 1.3.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.
Files changed (155) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +117 -47
  3. package/bin/naome.js +65 -12
  4. package/crates/naome-cli/Cargo.toml +1 -1
  5. package/crates/naome-cli/src/context_commands.rs +47 -0
  6. package/crates/naome-cli/src/dispatcher.rs +12 -2
  7. package/crates/naome-cli/src/main.rs +78 -29
  8. package/crates/naome-cli/src/quality_commands.rs +238 -34
  9. package/crates/naome-cli/src/quality_output.rs +34 -0
  10. package/crates/naome-cli/src/quality_reconcile_command.rs +45 -0
  11. package/crates/naome-cli/src/repository_model_commands.rs +84 -0
  12. package/crates/naome-cli/src/task_commands.rs +62 -0
  13. package/crates/naome-cli/src/workflow_commands.rs +120 -3
  14. package/crates/naome-core/Cargo.toml +1 -1
  15. package/crates/naome-core/src/context/helpers.rs +75 -0
  16. package/crates/naome-core/src/context/select.rs +134 -0
  17. package/crates/naome-core/src/context/types.rs +43 -0
  18. package/crates/naome-core/src/context.rs +6 -0
  19. package/crates/naome-core/src/decision/states.rs +1 -1
  20. package/crates/naome-core/src/decision.rs +4 -1
  21. package/crates/naome-core/src/git.rs +4 -2
  22. package/crates/naome-core/src/install_plan.rs +20 -0
  23. package/crates/naome-core/src/journal.rs +2 -7
  24. package/crates/naome-core/src/lib.rs +35 -8
  25. package/crates/naome-core/src/quality/adapter_ios.rs +131 -0
  26. package/crates/naome-core/src/quality/adapter_support.rs +67 -0
  27. package/crates/naome-core/src/quality/adapters.rs +81 -18
  28. package/crates/naome-core/src/quality/baseline.rs +8 -0
  29. package/crates/naome-core/src/quality/cache.rs +151 -0
  30. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +19 -8
  31. package/crates/naome-core/src/quality/checks/near_duplicates.rs +4 -2
  32. package/crates/naome-core/src/quality/checks.rs +7 -8
  33. package/crates/naome-core/src/quality/cleanup.rs +36 -3
  34. package/crates/naome-core/src/quality/config.rs +21 -3
  35. package/crates/naome-core/src/quality/mod.rs +189 -10
  36. package/crates/naome-core/src/quality/reconcile.rs +138 -0
  37. package/crates/naome-core/src/quality/reconcile_anchors.rs +64 -0
  38. package/crates/naome-core/src/quality/scanner/analysis/normalize.rs +78 -0
  39. package/crates/naome-core/src/quality/scanner/analysis.rs +175 -0
  40. package/crates/naome-core/src/quality/scanner/repo_paths.rs +39 -3
  41. package/crates/naome-core/src/quality/scanner.rs +235 -217
  42. package/crates/naome-core/src/quality/semantic/checks.rs +151 -0
  43. package/crates/naome-core/src/quality/semantic/extract.rs +158 -0
  44. package/crates/naome-core/src/quality/semantic/model.rs +85 -0
  45. package/crates/naome-core/src/quality/semantic/route.rs +52 -0
  46. package/crates/naome-core/src/quality/semantic.rs +68 -0
  47. package/crates/naome-core/src/quality/structure/adapter_ios.rs +149 -0
  48. package/crates/naome-core/src/quality/structure/adapters.rs +60 -42
  49. package/crates/naome-core/src/quality/structure/checks/directory.rs +13 -21
  50. package/crates/naome-core/src/quality/structure/checks.rs +1 -1
  51. package/crates/naome-core/src/quality/structure/classify/roles.rs +51 -5
  52. package/crates/naome-core/src/quality/structure/classify.rs +52 -0
  53. package/crates/naome-core/src/quality/structure/config.rs +24 -3
  54. package/crates/naome-core/src/quality/structure/mod.rs +5 -2
  55. package/crates/naome-core/src/quality/structure/model.rs +8 -1
  56. package/crates/naome-core/src/quality/types.rs +59 -2
  57. package/crates/naome-core/src/repository_model/detect.rs +188 -0
  58. package/crates/naome-core/src/repository_model/explain.rs +121 -0
  59. package/crates/naome-core/src/repository_model/path_scan.rs +67 -0
  60. package/crates/naome-core/src/repository_model/path_support.rs +59 -0
  61. package/crates/naome-core/src/repository_model/types.rs +152 -0
  62. package/crates/naome-core/src/repository_model/world.rs +48 -0
  63. package/crates/naome-core/src/repository_model/world_adapters.rs +145 -0
  64. package/crates/naome-core/src/repository_model/world_path_facts.rs +55 -0
  65. package/crates/naome-core/src/repository_model/world_paths.rs +168 -0
  66. package/crates/naome-core/src/repository_model.rs +164 -0
  67. package/crates/naome-core/src/route/builtin_checks.rs +41 -16
  68. package/crates/naome-core/src/task_ledger/import.rs +142 -0
  69. package/crates/naome-core/src/task_ledger/model.rs +13 -0
  70. package/crates/naome-core/src/task_ledger/proof_record.rs +52 -0
  71. package/crates/naome-core/src/task_ledger/read.rs +118 -0
  72. package/crates/naome-core/src/task_ledger/render.rs +55 -0
  73. package/crates/naome-core/src/task_ledger/write.rs +38 -0
  74. package/crates/naome-core/src/task_ledger.rs +48 -0
  75. package/crates/naome-core/src/task_state/api.rs +4 -2
  76. package/crates/naome-core/src/task_state/completed_refresh.rs +5 -16
  77. package/crates/naome-core/src/task_state/diff.rs +2 -2
  78. package/crates/naome-core/src/task_state/evidence.rs +8 -3
  79. package/crates/naome-core/src/task_state/mod.rs +1 -1
  80. package/crates/naome-core/src/task_state/progress.rs +13 -0
  81. package/crates/naome-core/src/task_state/proof_model.rs +8 -8
  82. package/crates/naome-core/src/task_state/repair.rs +2 -2
  83. package/crates/naome-core/src/task_state/task_diff_api.rs +9 -18
  84. package/crates/naome-core/src/task_state/types.rs +24 -0
  85. package/crates/naome-core/src/verification.rs +29 -18
  86. package/crates/naome-core/src/workflow/agent/capability.rs +194 -0
  87. package/crates/naome-core/src/workflow/agent/context_delta.rs +42 -0
  88. package/crates/naome-core/src/workflow/agent/decision.rs +32 -0
  89. package/crates/naome-core/src/workflow/agent/execution.rs +80 -0
  90. package/crates/naome-core/src/workflow/agent/proof.rs +24 -0
  91. package/crates/naome-core/src/workflow/agent/support.rs +58 -0
  92. package/crates/naome-core/src/workflow/agent/watchdog.rs +47 -0
  93. package/crates/naome-core/src/workflow/agent.rs +34 -0
  94. package/crates/naome-core/src/workflow/agent_types.rs +105 -0
  95. package/crates/naome-core/src/workflow/doctor.rs +183 -0
  96. package/crates/naome-core/src/workflow/mod.rs +13 -0
  97. package/crates/naome-core/src/workflow/mutation.rs +1 -2
  98. package/crates/naome-core/src/workflow/output.rs +8 -2
  99. package/crates/naome-core/src/workflow/phase_inference.rs +1 -1
  100. package/crates/naome-core/tests/context.rs +99 -0
  101. package/crates/naome-core/tests/harness_health.rs +4 -0
  102. package/crates/naome-core/tests/install_plan.rs +14 -0
  103. package/crates/naome-core/tests/quality.rs +190 -5
  104. package/crates/naome-core/tests/quality_performance.rs +268 -0
  105. package/crates/naome-core/tests/quality_structure_adapters.rs +39 -0
  106. package/crates/naome-core/tests/quality_structure_policy.rs +19 -0
  107. package/crates/naome-core/tests/repo_support/mod.rs +5 -1
  108. package/crates/naome-core/tests/repo_support/verification_values.rs +55 -0
  109. package/crates/naome-core/tests/repository_model.rs +281 -0
  110. package/crates/naome-core/tests/route_user_diff.rs +59 -7
  111. package/crates/naome-core/tests/semantic_legacy.rs +174 -0
  112. package/crates/naome-core/tests/task_ledger.rs +328 -0
  113. package/crates/naome-core/tests/task_state.rs +28 -0
  114. package/crates/naome-core/tests/verification.rs +29 -36
  115. package/crates/naome-core/tests/workflow_agent.rs +233 -0
  116. package/crates/naome-core/tests/workflow_agent_support/mod.rs +159 -0
  117. package/crates/naome-core/tests/workflow_doctor.rs +45 -0
  118. package/crates/naome-core/tests/workflow_policy.rs +6 -1
  119. package/installer/codex-hooks.js +121 -0
  120. package/installer/context.js +10 -0
  121. package/installer/filesystem.js +4 -0
  122. package/installer/flows.js +8 -4
  123. package/installer/git-boundary.js +1 -0
  124. package/installer/harness-files.js +6 -0
  125. package/installer/install-plan.js +4 -0
  126. package/installer/main.js +1 -1
  127. package/installer/native.js +1 -1
  128. package/native/darwin-arm64/naome +0 -0
  129. package/native/linux-x64/naome +0 -0
  130. package/package.json +1 -1
  131. package/templates/naome-root/.codex/config.toml +2 -0
  132. package/templates/naome-root/.codex/hooks.json +70 -0
  133. package/templates/naome-root/.naome/bin/check-harness-health.js +8 -6
  134. package/templates/naome-root/.naome/bin/check-task-state.js +12 -7
  135. package/templates/naome-root/.naome/bin/codex-hook-io.js +122 -0
  136. package/templates/naome-root/.naome/bin/codex-hook-policy.js +180 -0
  137. package/templates/naome-root/.naome/bin/codex-hook-runtime.js +174 -0
  138. package/templates/naome-root/.naome/bin/codex-hook.js +6 -0
  139. package/templates/naome-root/.naome/bin/naome.js +45 -7
  140. package/templates/naome-root/.naome/manifest.json +12 -6
  141. package/templates/naome-root/.naome/repository-model.json +6 -0
  142. package/templates/naome-root/.naome/repository-quality.json +3 -1
  143. package/templates/naome-root/.naome/verification.json +15 -1
  144. package/templates/naome-root/.naomeignore +1 -0
  145. package/templates/naome-root/AGENTS.md +38 -83
  146. package/templates/naome-root/docs/naome/agent-workflow.md +66 -28
  147. package/templates/naome-root/docs/naome/codex-hooks.md +82 -0
  148. package/templates/naome-root/docs/naome/context-economy.md +73 -0
  149. package/templates/naome-root/docs/naome/first-run.md +25 -14
  150. package/templates/naome-root/docs/naome/index.md +18 -10
  151. package/templates/naome-root/docs/naome/repository-model.md +92 -0
  152. package/templates/naome-root/docs/naome/repository-quality.md +104 -5
  153. package/templates/naome-root/docs/naome/repository-structure.md +10 -3
  154. package/templates/naome-root/docs/naome/task-ledger.md +71 -0
  155. package/templates/naome-root/docs/naome/testing.md +16 -3
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+
3
+ const { createHash } = require("node:crypto");
4
+ const { existsSync, mkdirSync, readFileSync, writeFileSync } = require("node:fs");
5
+ const { dirname } = require("node:path");
6
+ const { spawnSync } = require("node:child_process");
7
+
8
+ const CACHE = ".naome/cache/codex-hook-state.json";
9
+
10
+ function readPayload() {
11
+ try {
12
+ return JSON.parse(readFileSync(0, "utf8") || "{}");
13
+ } catch {
14
+ return {};
15
+ }
16
+ }
17
+
18
+ function eventName(payload) {
19
+ const index = process.argv.indexOf("--event");
20
+ return index !== -1 && process.argv[index + 1] ? process.argv[index + 1] : payload?.hook_event_name || payload?.event || "";
21
+ }
22
+
23
+ function commandOf(payload) {
24
+ return [payload?.tool_input?.command, payload?.toolInput?.command, payload?.command].find((value) => typeof value === "string" && value);
25
+ }
26
+
27
+ function promptOf(payload) {
28
+ return [payload?.prompt, payload?.user_prompt, payload?.tool_input?.prompt, payload?.toolInput?.prompt, payload?.reason, payload?.message].find((value) => typeof value === "string" && value) || "";
29
+ }
30
+
31
+ function readJson(path) {
32
+ try {
33
+ return existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ function parseJson(value) {
40
+ try {
41
+ return JSON.parse(value);
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
47
+ function readCache() {
48
+ return readJson(CACHE) || {};
49
+ }
50
+
51
+ function writeCache(value) {
52
+ try {
53
+ mkdirSync(dirname(CACHE), { recursive: true });
54
+ writeFileSync(CACHE, `${JSON.stringify(value)}\n`);
55
+ } catch {
56
+ // Best-effort cache only.
57
+ }
58
+ }
59
+
60
+ function run(command, args, env = process.env) {
61
+ return spawnSync(command, args, { cwd: process.cwd(), encoding: "utf8", env });
62
+ }
63
+
64
+ function runGit(args) {
65
+ return run("git", args);
66
+ }
67
+
68
+ function runNaome(args) {
69
+ return run(process.execPath, [".naome/bin/naome.js", ...args], { ...process.env, NO_COLOR: "1" });
70
+ }
71
+
72
+ function taskCheck(args) {
73
+ return run(process.execPath, [".naome/bin/check-task-state.js", ...args]);
74
+ }
75
+
76
+ function diffSignature() {
77
+ return createHash("sha256")
78
+ .update(runGit(["status", "--porcelain=v1"]).stdout || "")
79
+ .update(runGit(["diff", "--no-ext-diff", "HEAD", "--"]).stdout || "")
80
+ .digest("hex");
81
+ }
82
+
83
+ function decide(decision, reason, messages) {
84
+ write(decisionObject(decision, reason, messages));
85
+ }
86
+
87
+ function decisionObject(decision, reason, messages) {
88
+ if (decision !== "block") {
89
+ return {};
90
+ }
91
+ const detail = messages.filter(Boolean).join("; ");
92
+ return { decision, reason: compact(detail ? `${reason}: ${detail}` : reason) };
93
+ }
94
+
95
+ function write(value) {
96
+ process.stdout.write(`${JSON.stringify(value)}\n`);
97
+ }
98
+
99
+ function compact(value) {
100
+ const text = String(value);
101
+ return text.length > 500 ? `${text.slice(0, 497)}...` : text;
102
+ }
103
+
104
+ module.exports = {
105
+ commandOf,
106
+ compact,
107
+ decide,
108
+ decisionObject,
109
+ diffSignature,
110
+ eventName,
111
+ parseJson,
112
+ promptOf,
113
+ readCache,
114
+ readJson,
115
+ readPayload,
116
+ run,
117
+ runGit,
118
+ runNaome,
119
+ taskCheck,
120
+ write,
121
+ writeCache
122
+ };
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+
3
+ const { readFileSync } = require("node:fs");
4
+
5
+ const CHEAP = new Set(["diff-check", "repository-quality-check", "repository-semantic-check", "task-state-progress-check"]);
6
+ const EXPENSIVE = new Set(["installer-tests", "rust-build", "package-dry-run", "harness-health-tests", "task-state-tests", "verification-contract-tests"]);
7
+
8
+ function classifyPrompt(prompt) {
9
+ const value = prompt.toLowerCase();
10
+ if (/status|where are we|progress/.test(value)) return "status_request";
11
+ if (/review|comment|pr feedback/.test(value)) return "review";
12
+ if (/commit/.test(value)) return "commit";
13
+ if (/push/.test(value) && !/ci|check|fail/.test(value)) return "push";
14
+ if (/ci|check|failing|failed|workflow|actions/.test(value)) return "ci_fix";
15
+ if (/continue|keep going|resume|do so/.test(value)) return "continuation";
16
+ return "new_task";
17
+ }
18
+
19
+ function riskyPromptCodes(prompt) {
20
+ const value = prompt.toLowerCase();
21
+ return [
22
+ ["reset|clean|rm\\s+-rf", "risky-destructive"],
23
+ ["force push|--force|--force-with-lease|\\spush\\s+-f", "risky-force-push"],
24
+ ["--no-verify|bypass.*hook|skip.*hook", "risky-hook-bypass"],
25
+ ["publish|release|npm publish", "risky-release"]
26
+ ].filter(([pattern]) => new RegExp(pattern).test(value)).map(([, code]) => code);
27
+ }
28
+
29
+ function permissionRiskCodes(value) {
30
+ const lower = value.toLowerCase();
31
+ return [...new Set([
32
+ ["https?:|network|fetch|curl|wget|push|pull|gh\\s+|npm\\s+(install|publish)", "network"],
33
+ ["\\bgit\\b|push|commit|merge|rebase", "git"],
34
+ ["write|stage|commit|push|apply|patch|install|sync", "write"],
35
+ ["publish|npm publish", "publish"],
36
+ ["release|tag", "release"],
37
+ ["reset|clean|--force|-f\\b|rm\\s+-rf", "destructive"]
38
+ ].filter(([pattern]) => new RegExp(pattern).test(lower)).map(([, code]) => code))];
39
+ }
40
+
41
+ function hardBlockedCommand(command) {
42
+ const value = command.replace(/\s+/g, " ").trim();
43
+ return [
44
+ [/(\bgit\s+reset\b.*\B--hard\b|\bgit\s+reset\s+--hard\b)/, "destructive-git-reset-hard: hard reset requires explicit human review."],
45
+ [/\bgit\s+clean\b.*(^|\s)-[^\s]*f|\bgit\s+clean\b.*--force\b/, "destructive-git-clean: forced clean requires explicit human review."],
46
+ [/\brm\b(?=.*(?:^|\s)-[^\s]*r)(?=.*(?:^|\s)-[^\s]*f)(?=.*(?:^|\s)(?:--\s+)?(?:\.\/)?(?:[^/\s]+\/)*\.git(?:\/|\s|$))/, "destructive-git-dir-removal: removing .git is blocked."],
47
+ [/--no-verify\b/, "hook-bypass-command: --no-verify is blocked."],
48
+ [/\bgit\s+push\b.*(?:--force(?:-with-lease)?\b|(^|\s)-f(\s|$))/, "force-push-command: force pushes require fresh human review."]
49
+ ].find(([pattern]) => pattern.test(value))?.[1] || null;
50
+ }
51
+
52
+ function searchSegments(command) {
53
+ return command
54
+ .split(/&&|\|\||[;|]/)
55
+ .map((part) => part.trim().replace(/^(?:cd\s+\S+\s+)?/, ""))
56
+ .filter((part) => /^(rg|grep|find)\b/.test(part));
57
+ }
58
+
59
+ function forbiddenPath(payload, command) {
60
+ const input = payload?.tool_input || payload?.toolInput || {};
61
+ const values = [command, ...["path", "file_path", "filepath", "target_file", "pattern"].map((key) => input[key])].filter(Boolean);
62
+ return values.some((value) => tokens(value).some(blockedPath)) ? "read-boundary-violation: command or tool input touches a path blocked by .naomeignore." : null;
63
+ }
64
+
65
+ function blockedPath(path) {
66
+ if (/(^|\/)\.naome\/archive(\/|$)/.test(path)) return true;
67
+ return ignoredPatterns().some((pattern) => pathMatches(path, pattern));
68
+ }
69
+
70
+ function tokens(value) {
71
+ return String(value).replace(/\\/g, "/").split(/[\s"'`,]+/).map((token) => token.replace(/^\.\//, "").replace(/[):;]+$/, "")).filter(Boolean);
72
+ }
73
+
74
+ function ignoredPatterns() {
75
+ try {
76
+ return readFileSync(".naomeignore", "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
77
+ } catch {
78
+ return [".naome/archive/"];
79
+ }
80
+ }
81
+
82
+ function pathMatches(path, pattern) {
83
+ const actual = String(path).replace(/\\/g, "/");
84
+ const glob = String(pattern).replace(/\\/g, "/");
85
+ if (glob.endsWith("/")) return actual.startsWith(glob);
86
+ if (glob.endsWith("/**")) return actual.startsWith(glob.slice(0, -3));
87
+ if (!glob.includes("*")) return actual === glob;
88
+ return new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*")}$`).test(actual);
89
+ }
90
+
91
+ function looksLikeWriteTool(payload, command) {
92
+ const name = String(payload?.tool_name || payload?.toolName || payload?.name || "").toLowerCase();
93
+ if (/edit|write|patch|notebookedit|multiedit/.test(name)) return true;
94
+ if (!command) return false;
95
+ if (/^(git status|git diff|rg\b|grep\b|find\b|sed\b|cat\b|nl\b|wc\b|ls\b|pwd\b)/.test(command.trim())) return false;
96
+ return />|>>|\b(apply_patch|cp|mv|touch|mkdir|rm|npm install|cargo build|node --test|writeFileSync)\b/.test(command);
97
+ }
98
+
99
+ function touchedPaths(payload, command) {
100
+ const paths = [];
101
+ collectInputPaths(payload?.tool_input || payload?.toolInput || {}, paths);
102
+ collectCommandPaths(command, paths);
103
+ return [...new Set(paths.map(cleanPath).filter(Boolean))];
104
+ }
105
+
106
+ function collectInputPaths(value, paths) {
107
+ if (Array.isArray(value)) return value.forEach((entry) => collectInputPaths(entry, paths));
108
+ if (!value || typeof value !== "object") return;
109
+ for (const [key, child] of Object.entries(value)) {
110
+ if (/^(path|paths|file|files|file_path|filepath|target_file)$/.test(key)) collectPathValue(child, paths);
111
+ if (child && typeof child === "object") collectInputPaths(child, paths);
112
+ }
113
+ }
114
+
115
+ function collectPathValue(value, paths) {
116
+ if (Array.isArray(value)) return value.forEach((entry) => collectPathValue(entry, paths));
117
+ if (typeof value === "string") paths.push(value);
118
+ }
119
+
120
+ function collectCommandPaths(command, paths) {
121
+ if (!command) return;
122
+ for (const segment of command.split(/&&|\|\||[;]/).map((part) => part.trim())) {
123
+ const parts = tokens(segment);
124
+ if (/^(touch|mkdir|rm)$/.test(parts[0])) paths.push(...parts.slice(1).filter((part) => !part.startsWith("-")));
125
+ if (/^(cp|mv)$/.test(parts[0])) paths.push(...parts.slice(1).filter((part) => !part.startsWith("-")).slice(-1));
126
+ }
127
+ }
128
+
129
+ function cleanPath(path) {
130
+ return String(path).replace(/\\/g, "/").replace(/^\.\//, "").replace(/[):;]+$/, "");
131
+ }
132
+
133
+ function scopeDrift(paths, allowed) {
134
+ if (!Array.isArray(allowed)) return [];
135
+ return paths.filter((path) => !isControlStatePath(path) && !allowed.some((pattern) => pathMatches(path, pattern)));
136
+ }
137
+
138
+ function isControlStatePath(path) {
139
+ return path === ".naome/task-state.json" || path.startsWith(".naome/tasks/") || path.startsWith(".naome/cache/");
140
+ }
141
+
142
+ function owedProof(paths, verification) {
143
+ const checks = new Set();
144
+ for (const type of verification?.changeTypes || []) {
145
+ if (paths.some((path) => (type.paths || []).some((pattern) => pathMatches(path, pattern)))) {
146
+ for (const check of type.requiredChecks || []) if (!CHEAP.has(check)) checks.add(check);
147
+ }
148
+ }
149
+ return [...checks].sort();
150
+ }
151
+
152
+ function expensiveProof(checks) {
153
+ return checks.filter((check) => EXPENSIVE.has(check));
154
+ }
155
+
156
+ function pushUnique(codes, code, messages, message) {
157
+ if (!codes.includes(code)) codes.push(code);
158
+ if (!messages.includes(message)) messages.push(message);
159
+ }
160
+
161
+ function primaryReason(codes) {
162
+ if (codes.includes("scope-drift")) return "scope-drift";
163
+ if (codes.some((code) => code.endsWith("-failed"))) return "cheap-gate-failed";
164
+ return codes[0] || "naome-hook-warning";
165
+ }
166
+
167
+ module.exports = {
168
+ classifyPrompt,
169
+ expensiveProof,
170
+ forbiddenPath,
171
+ hardBlockedCommand,
172
+ looksLikeWriteTool,
173
+ owedProof,
174
+ permissionRiskCodes,
175
+ primaryReason,
176
+ pushUnique,
177
+ riskyPromptCodes,
178
+ scopeDrift,
179
+ searchSegments, touchedPaths
180
+ };
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+
3
+ const io = require("./codex-hook-io.js");
4
+ const policy = require("./codex-hook-policy.js");
5
+
6
+ function handleCodexHook() {
7
+ const payload = io.readPayload();
8
+ const event = io.eventName(payload);
9
+ try {
10
+ if (event === "UserPromptSubmit") return userPrompt(payload); if (event === "PreToolUse") return preTool(payload);
11
+ if (event === "PermissionRequest") return permission(payload); if (event === "PostToolUse") return postTool(payload);
12
+ if (event === "Stop") return stop(payload);
13
+ } catch {
14
+ return io.write({});
15
+ }
16
+ io.write({});
17
+ }
18
+
19
+ function userPrompt(payload) {
20
+ const prompt = io.promptOf(payload);
21
+ const classification = policy.classifyPrompt(prompt);
22
+ const risky = policy.riskyPromptCodes(prompt);
23
+ const status = io.parseJson(io.runNaome(["status", "--json"]).stdout);
24
+ const codes = [`prompt-${classification}`, ...risky];
25
+ if (status?.state === "dirty_unowned_diff") codes.push("repo-dirty-unowned");
26
+ if (["new_task", "ci_fix"].includes(classification)) codes.push("route-required", "context-selection-required");
27
+ io.decide("warn", "prompt-guidance", [`prompt:${classification}`, status?.state ? `repo:${status.state}` : null, risky.length ? `risk:${risky.join(",")}` : null, ["new_task", "ci_fix"].includes(classification) ? "run route and context select before feature work" : null], { classification, reasonCodes: codes });
28
+ }
29
+
30
+ function preTool(payload) {
31
+ const command = io.commandOf(payload);
32
+ const boundary = policy.forbiddenPath(payload, command);
33
+ if (boundary) return io.decide("block", boundary, [boundary], { reasonCodes: [boundary.split(":")[0]] });
34
+ if (!command) return io.write({});
35
+ const hard = policy.hardBlockedCommand(command);
36
+ if (hard) return io.decide("block", hard, [hard], { reasonCodes: [hard.split(":")[0]] });
37
+ const preflight = commitPushPreflight(command);
38
+ if (preflight) return io.decide("block", preflight, [preflight], { reasonCodes: [preflight.split(":")[0]] });
39
+ const search = validateSearch(command);
40
+ if (search) return io.decide("block", search, [search], { reasonCodes: [search.split(":")[0]] });
41
+ io.write({});
42
+ }
43
+
44
+ function permission(payload) {
45
+ const risks = policy.permissionRiskCodes(`${io.commandOf(payload) || ""} ${io.promptOf(payload)}`);
46
+ if (!risks.length) return io.write({});
47
+ io.decide("warn", "permission-risk-summary", [`permission risks: ${risks.join(",")}; approve only if this matches the current NAOME task.`], { riskCodes: risks, reasonCodes: ["permission-risk-summary"] });
48
+ }
49
+
50
+ function postTool(payload) {
51
+ const command = io.commandOf(payload);
52
+ const changedPaths = changedFiles();
53
+ if (!changedPaths.length) return io.write({});
54
+ if (!policy.looksLikeWriteTool(payload, command)) {
55
+ const cache = io.readCache();
56
+ io.writeCache({ ...cache, postToolUse: { signature: io.diffSignature(), decision: {} } });
57
+ return io.write({});
58
+ }
59
+ const checkedPaths = postToolCheckedPaths(payload, command, changedPaths);
60
+ if (!checkedPaths.length) return io.write({});
61
+ const signature = `${io.diffSignature()}:${checkedPaths.join("\0")}`;
62
+ const cache = io.readCache();
63
+ if (cache.postToolUse?.signature === signature) return io.write({});
64
+ const result = changedState(checkedPaths, { pathScoped: true });
65
+ const decision = result.codes.length ? io.decisionObject("warn", policy.primaryReason(result.codes), result.messages, { reasonCodes: result.codes, changedPaths: checkedPaths, owedProof: result.expensiveOwed, cheapGates: result.gates }) : {};
66
+ io.writeCache({ ...cache, postToolUse: { signature, decision, changedPaths, checkedPaths, cheapGateCommands: result.gateCommands } });
67
+ io.write(decision);
68
+ }
69
+
70
+ function stop(payload) {
71
+ const changedPaths = changedFiles();
72
+ const result = changedState(changedPaths);
73
+ for (const check of [io.taskCheck([]), io.taskCheck(["--progress"])]) {
74
+ const output = `${check.stdout || ""}\n${check.stderr || ""}`;
75
+ if (check.status && /outside allowedPaths|scope drift/i.test(output)) policy.pushUnique(result.codes, "scope-drift", result.messages, "scope drift blocks completion");
76
+ if (check.status && /missing proof|proofResults missing|failed proof/i.test(output)) policy.pushUnique(result.codes, "missing-proof", result.messages, "missing required proof blocks completion");
77
+ }
78
+ const prompt = io.promptOf(payload);
79
+ if (/push/i.test(prompt) && /\[ahead [1-9]/.test(io.runGit(["status", "--short", "--branch"]).stdout || "")) {
80
+ policy.pushUnique(result.codes, "unpushed-branch", result.messages, "branch has unpushed commits");
81
+ }
82
+ if (/resolve|review comment|github comment/i.test(prompt) && /CHANGES_REQUESTED/.test(io.run("gh", ["pr", "view", "--json", "reviewDecision"]).stdout || "")) {
83
+ policy.pushUnique(result.codes, "unresolved-review-comments", result.messages, "GitHub review still appears unresolved");
84
+ }
85
+ if (result.codes.length) return io.decide("block", policy.primaryReason(result.codes), result.messages, { reasonCodes: result.codes, changedPaths, cheapGates: result.gates });
86
+ io.write({});
87
+ }
88
+
89
+ function changedState(changedPaths, options = {}) {
90
+ const codes = [];
91
+ const messages = [];
92
+ const state = io.readJson(".naome/task-state.json");
93
+ const drift = policy.scopeDrift(changedPaths, state?.activeTask?.allowedPaths);
94
+ if (drift.length) policy.pushUnique(codes, "scope-drift", messages, `scope drift: ${drift.join(", ")}`);
95
+ const expensiveOwed = missingExpensiveProof(changedPaths, policy.expensiveProof(policy.owedProof(changedPaths, io.readJson(".naome/verification.json"))), state?.activeTask);
96
+ if (expensiveOwed.length) policy.pushUnique(codes, "owed-proof", messages, `owed proof: ${expensiveOwed.join(", ")}`);
97
+ if (repoStale()) policy.pushUnique(codes, "repository-model-stale", messages, "repository model is stale");
98
+ const gatePaths = options.pathScoped ? changedPaths : [];
99
+ const gates = cheapGates(gatePaths);
100
+ for (const gate of gates) policy.pushUnique(codes, gate.code, messages, gate.message);
101
+ return { codes, messages, expensiveOwed, gates, gateCommands: cheapGateCommands(gatePaths) };
102
+ }
103
+
104
+ function commitPushPreflight(command) {
105
+ if (!/\bgit\s+(commit|push)\b/.test(command.replace(/\s+/g, " "))) return null;
106
+ if (/\bgit\s+commit\b/.test(command)) return "commit-preflight: use `node .naome/bin/naome.js commit -m ...` so NAOME owns staging, proof, and commit gates.";
107
+ for (const gate of cheapGates()) return `push-preflight: ${gate.checkId} failed before git push.`;
108
+ return repoStale() ? "push-preflight: repository-model-stale failed before git push." : null;
109
+ }
110
+
111
+ function cheapGates(paths = []) {
112
+ return cheapGateChecks(paths).flatMap(([checkId, command, fn]) => fn().status ? [{ code: `${checkId}-failed`, checkId, command, message: `cheap gate failed: ${checkId}` }] : []);
113
+ }
114
+ function cheapGateCommands(paths = []) {
115
+ return cheapGateChecks(paths).map(([, command]) => command);
116
+ }
117
+ function cheapGateChecks(paths = []) {
118
+ const pathArgs = paths.flatMap((path) => ["--path", path]);
119
+ const displayPaths = paths.map(shellPath).join(" ");
120
+ const checks = [
121
+ ["diff-check", paths.length ? `git diff --check -- ${displayPaths}` : "git diff --check", () => io.runGit(paths.length ? ["diff", "--check", "--", ...paths] : ["diff", "--check"])],
122
+ ["repository-quality-check", paths.length ? `node .naome/bin/naome.js quality check ${pathArgs.join(" ")}` : "node .naome/bin/naome.js quality check --changed", () => io.runNaome(paths.length ? ["quality", "check", ...pathArgs] : ["quality", "check", "--changed"])],
123
+ ["repository-semantic-check", paths.length ? `node .naome/bin/naome.js semantic check ${pathArgs.join(" ")}` : "node .naome/bin/naome.js semantic check --changed", () => io.runNaome(paths.length ? ["semantic", "check", ...pathArgs] : ["semantic", "check", "--changed"])]
124
+ ];
125
+ if (!paths.length && hasActiveTask()) checks.push(["task-state-progress-check", "node .naome/bin/check-task-state.js --progress", () => io.taskCheck(["--progress"])]);
126
+ return checks;
127
+ }
128
+ function validateSearch(command) {
129
+ for (const segment of policy.searchSegments(command)) {
130
+ const result = io.runNaome(["workflow", "check-search", "--command", segment, "--json"]);
131
+ if (result.status === 0) continue;
132
+ const ids = io.parseJson(result.stdout)?.findings?.map((finding) => finding.id).filter(Boolean);
133
+ return ids?.length ? `${ids.join(",")}: NAOME rejected this broad search command.` : io.compact(`unsafe-search-command: ${result.stdout || result.stderr}`);
134
+ }
135
+ return null;
136
+ }
137
+ function changedFiles() {
138
+ const result = io.runGit(["status", "--porcelain=v1"]);
139
+ return result.status ? [] : result.stdout.split(/\r?\n/).filter(Boolean).map((line) => line.replace(/^..\s+/, "").split(" -> ").pop()).filter((path, index, paths) => path && paths.indexOf(path) === index);
140
+ }
141
+ function postToolCheckedPaths(payload, command, changedPaths) {
142
+ const touched = policy.touchedPaths(payload, command);
143
+ if (!touched.length) return changedPaths;
144
+ return changedPaths.filter((path) => touched.some((touch) => path === touch || path.startsWith(`${touch}/`)));
145
+ }
146
+ function shellPath(path) {
147
+ return /[^A-Za-z0-9_./:-]/.test(path) ? JSON.stringify(path) : path;
148
+ }
149
+ function repoStale() {
150
+ const result = io.runNaome(["repo", "check", "--json"]);
151
+ return result.status !== 0 && /repository model is stale|repository-model-stale/i.test(`${result.stdout}\n${result.stderr}`);
152
+ }
153
+
154
+ function hasActiveTask() {
155
+ const state = io.readJson(".naome/task-state.json"); return Boolean(state?.activeTask && state.status === "implementing");
156
+ }
157
+ function missingExpensiveProof(changedPaths, checkIds, activeTask) {
158
+ const proofPaths = changedPaths.filter((path) => !isControlStatePath(path));
159
+ return proofPaths.length ? checkIds.filter((checkId) => !proofCoversChangedPaths(activeTask, checkId, proofPaths)) : [];
160
+ }
161
+ function proofCoversChangedPaths(activeTask, checkId, changedPaths) {
162
+ const proof = (activeTask?.proofResults || []).find((entry) => entry.checkId === checkId && entry.exitCode === 0);
163
+ const evidence = Array.isArray(proof?.evidence) ? proof.evidence : [];
164
+ return changedPaths.every((changedPath) => evidence.some((path) => evidenceCoversPath(path, changedPath)));
165
+ }
166
+ function evidenceCoversPath(evidencePath, changedPath) {
167
+ const evidence = String(evidencePath);
168
+ return evidence === changedPath || (evidence.endsWith("/") && String(changedPath).startsWith(evidence)) || (evidence.endsWith("/**") && String(changedPath).startsWith(evidence.slice(0, -3)));
169
+ }
170
+ function isControlStatePath(path) {
171
+ return path === ".naome/task-state.json" || path.startsWith(".naome/tasks/") || path.startsWith(".naome/cache/");
172
+ }
173
+
174
+ module.exports = { handleCodexHook };
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { handleCodexHook } = require("./codex-hook-runtime.js");
5
+
6
+ handleCodexHook();
@@ -27,7 +27,7 @@ function main(argv) {
27
27
  return;
28
28
  }
29
29
 
30
- if (["status", "next", "intent", "route", "explain", "quality", "structure", "cleanup", "refresh-integrity", "workflow"].includes(command)) {
30
+ if (["status", "next", "intent", "route", "explain", "context", "doctor", "task", "quality", "semantic", "repo", "structure", "cleanup", "refresh-integrity", "workflow"].includes(command)) {
31
31
  runNativeDecisionCommand(command, args);
32
32
  return;
33
33
  }
@@ -143,6 +143,10 @@ function commit(args) {
143
143
  }
144
144
 
145
145
  const message = parseCommitMessage(args);
146
+ const renderResult = spawnNative(root, ["task", "render-state", "--write", "--json"]);
147
+ if (renderResult.status !== 0) {
148
+ fail(`Cannot render NAOME task-state projection: ${renderResult.stderr.trim() || renderResult.stdout.trim()}`);
149
+ }
146
150
  const taskState = readTaskState(root);
147
151
 
148
152
  if (taskState.status !== "complete") {
@@ -328,14 +332,17 @@ function gitHead(root) {
328
332
  }
329
333
 
330
334
  function findHarnessRoot(startPath) {
331
- return findAncestorWithMarker(startPath, [".naome", "task-state.json"]);
335
+ return findAncestorWithAnyMarker(startPath, [
336
+ [".naome", "task-state.json"],
337
+ [".naome", "tasks", "active.json"]
338
+ ]);
332
339
  }
333
340
 
334
- function findAncestorWithMarker(startPath, markerParts) {
341
+ function findAncestorWithAnyMarker(startPath, markers) {
335
342
  let current = resolve(startPath);
336
343
 
337
344
  while (true) {
338
- if (existsSync(join(current, ...markerParts))) {
345
+ if (markers.some((markerParts) => existsSync(join(current, ...markerParts)))) {
339
346
  return current;
340
347
  }
341
348
 
@@ -358,14 +365,32 @@ function printHelp() {
358
365
  "naome route --prompt <text> [--execute] [--json]",
359
366
  "naome explain --prompt-file <path> [--json]",
360
367
  "naome explain --prompt <text> [--json]",
361
- "naome quality init [--json]",
362
- "naome quality check --changed [--json]",
363
- "naome quality report [--json]",
368
+ "naome context select --changed [--json]",
369
+ "naome context select --prompt-file <path> [--json]",
370
+ "naome context select --prompt <text> [--json]",
371
+ "naome doctor [--json]",
372
+ "naome task render-state [--write] [--json]",
373
+ "naome task migrate-ledger [--write] [--json]",
374
+ "naome quality init [--baseline|--deep-baseline] [--json]",
375
+ "naome quality check --changed [--include-scanned-paths] [--json]",
376
+ "naome quality check --path <path> [--path <path>...] [--include-scanned-paths] [--json]",
377
+ "naome quality report [--deep] [--include-scanned-paths] [--json]",
378
+ "naome quality cache status [--json]",
379
+ "naome quality cache clear",
380
+ "naome semantic report [--deep] [--json]",
381
+ "naome semantic check --changed [--json]",
382
+ "naome semantic check --path <path> [--path <path>...] [--json]",
383
+ "naome semantic route --finding <id> [--json]",
384
+ "naome semantic loop [--json]",
385
+ "naome repo model [--write] [--json]",
386
+ "naome repo check [--json]",
387
+ "naome repo explain --path <path> [--json]",
364
388
  "naome structure report [--json]",
365
389
  "naome structure explain --path <path> [--json]",
366
390
  "naome cleanup plan [--json]",
367
391
  "naome cleanup route --path <path> [--json]",
368
392
  "naome refresh-integrity [--json]",
393
+ "naome workflow agent-plan|context-delta|proof-plan|capabilities|edit-watchdog|decision-gate|digest [--json]",
369
394
  "naome workflow search-profile|check-search|phases|processes|mutations [--json]",
370
395
  "naome install",
371
396
  "naome sync",
@@ -392,6 +417,19 @@ function installOrSync(command, args) {
392
417
  join(packageRoot, "bin", "naome-node.js"),
393
418
  ...args
394
419
  ], { stdio: "inherit" });
420
+ if (result.status === 0) {
421
+ const migration = spawnNative(root, ["task", "migrate-ledger", "--write", "--json"]);
422
+ if (migration.status !== 0) {
423
+ fail("task ledger migration failed after sync.");
424
+ }
425
+ const output = (migration.stdout || "").trim();
426
+ if (output) {
427
+ const parsed = JSON.parse(output);
428
+ if (parsed.updated && parsed.migration && parsed.migration.source === "task-state") {
429
+ console.log("task ledger migrated from task-state");
430
+ }
431
+ }
432
+ }
395
433
  process.exit(result.status === null ? 1 : result.status);
396
434
  }
397
435
 
@@ -3,15 +3,17 @@
3
3
  "installedAt": null,
4
4
  "integrity": {
5
5
  ".naome/bin/check-harness-health.js": "sha256:dc4de52b79c69600b9ba47b924e2c2b8de61a2cbfab6d1ccc0f1924d963db657",
6
- ".naome/bin/check-task-state.js": "sha256:43c02868072d0d13499aefba2e9a5ec9517d59539fd19ff0f11e3e4623a51b44",
7
- ".naome/bin/naome.js": "sha256:87eabd690bf55f0b0195446db46fa7c19bfa17395d31c2f34a9ef9339d2229e2",
6
+ ".naome/bin/check-task-state.js": "sha256:df54489a22b426180266e5e0fb5f9ec381477419f688435248afbf2b9b38ea81",
7
+ ".naome/bin/naome.js": "sha256:d343f367da21bf45272331eec2ea34862a0bf056978780c62d6cae02e0f7993a",
8
8
  ".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
9
9
  ".naome/task-contract.schema.json": "sha256:1b3b62350328d0d6d660e36d1d1baaa2b88718530db774f9ab2a9e2fcba369c8",
10
- "AGENTS.md": "sha256:9192ea81f90bb19f8043513c49b5da9e9598ee694da8356f345e7ccbca0e28df",
11
- "docs/naome/agent-workflow.md": "sha256:802eb34cf268fc9c5e7aefc28192efef4bf60302d30b3f78e5a61b876ce8a098",
10
+ "AGENTS.md": "sha256:e8b2fc786c1c72b69ba8f2b2ffce4f459e799c7453ce9ff4a9f6448a8f9e6b4f",
11
+ "docs/naome/agent-workflow.md": "sha256:78faeb4eed157b60f0b60bc43da36c713fc4a3436b93bd963ce5f3c12dc91f22",
12
+ "docs/naome/context-economy.md": "sha256:3ed5075815ecf4ada46a5e65438769310307c35759fcd46b13dc0b96e02bebd9",
12
13
  "docs/naome/execution.md": "sha256:bfc5d55838942ec8e3d790b59e3c634ff5bf6a2298265cef3dca9788a097eafb",
13
- "docs/naome/first-run.md": "sha256:a1dd0bd17ec9d71955a473cd2c4a615538e89a7d81e8f4e1015a50ab9efe3558",
14
- "docs/naome/index.md": "sha256:a674102cc801702dc77102afb59be0f5ab189dc638caf0bef358b9d6087d0742",
14
+ "docs/naome/first-run.md": "sha256:f1a2412f1b542e61c79b5ba65a3fdaa810b0f95cf79d9f734256418cdcfb19aa",
15
+ "docs/naome/index.md": "sha256:07ef776f49130319a5280bdb3ae38af22141708253f38eb983a4336fbae1b25a",
16
+ "docs/naome/task-ledger.md": "sha256:1e524085a2f88811941fd9f2f48ca826bc3e0e4816039d07e795637847714cbd",
15
17
  "docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1"
16
18
  },
17
19
  "machineOwned": [
@@ -24,7 +26,9 @@
24
26
  "docs/naome/index.md",
25
27
  "docs/naome/first-run.md",
26
28
  "docs/naome/agent-workflow.md",
29
+ "docs/naome/context-economy.md",
27
30
  "docs/naome/execution.md",
31
+ "docs/naome/task-ledger.md",
28
32
  "docs/naome/upgrade.md"
29
33
  ],
30
34
  "name": "naome",
@@ -36,12 +40,14 @@
36
40
  ".naome/task-state.json",
37
41
  ".naome/upgrade-state.json",
38
42
  ".naome/verification.json",
43
+ ".naome/repository-model.json",
39
44
  ".naome/repository-quality.json",
40
45
  ".naome/repository-structure.json",
41
46
  ".naome/repository-quality-baseline.json",
42
47
  "docs/naome/architecture.md",
43
48
  "docs/naome/decisions.md",
44
49
  "docs/naome/repo-profile.md",
50
+ "docs/naome/repository-model.md",
45
51
  "docs/naome/repository-quality.md",
46
52
  "docs/naome/repository-structure.md",
47
53
  "docs/naome/security.md",
@@ -0,0 +1,6 @@
1
+ {
2
+ "schema": "naome.repository-model.v2",
3
+ "version": 2,
4
+ "status": "ready",
5
+ "facts": []
6
+ }
@@ -13,7 +13,9 @@
13
13
  },
14
14
  "enabledAdapters": [],
15
15
  "disabledChecks": [],
16
- "ignoredPaths": [],
16
+ "ignoredPaths": [
17
+ ".naome/tasks/**"
18
+ ],
17
19
  "generatedPaths": [
18
20
  "**/*.min.js",
19
21
  "**/*.map",