@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.
Files changed (139) hide show
  1. package/Cargo.lock +2 -2
  2. package/README.md +108 -47
  3. package/bin/naome-node.js +2 -1579
  4. package/bin/naome.js +34 -5
  5. package/crates/naome-cli/Cargo.toml +1 -1
  6. package/crates/naome-cli/src/dispatcher.rs +7 -2
  7. package/crates/naome-cli/src/main.rs +37 -22
  8. package/crates/naome-cli/src/quality_commands.rs +317 -10
  9. package/crates/naome-cli/src/workflow_commands.rs +21 -1
  10. package/crates/naome-core/Cargo.toml +1 -1
  11. package/crates/naome-core/src/decision/checks.rs +64 -0
  12. package/crates/naome-core/src/decision/idle.rs +67 -0
  13. package/crates/naome-core/src/decision/json.rs +36 -0
  14. package/crates/naome-core/src/decision/states.rs +165 -0
  15. package/crates/naome-core/src/decision.rs +131 -353
  16. package/crates/naome-core/src/git.rs +4 -2
  17. package/crates/naome-core/src/install_plan.rs +4 -0
  18. package/crates/naome-core/src/lib.rs +12 -6
  19. package/crates/naome-core/src/paths.rs +3 -1
  20. package/crates/naome-core/src/quality/adapter_support.rs +89 -0
  21. package/crates/naome-core/src/quality/adapters.rs +20 -67
  22. package/crates/naome-core/src/quality/baseline.rs +8 -0
  23. package/crates/naome-core/src/quality/cache.rs +153 -0
  24. package/crates/naome-core/src/quality/checks/duplicate_blocks.rs +25 -11
  25. package/crates/naome-core/src/quality/checks/near_duplicates.rs +4 -2
  26. package/crates/naome-core/src/quality/checks.rs +7 -8
  27. package/crates/naome-core/src/quality/cleanup.rs +48 -3
  28. package/crates/naome-core/src/quality/config.rs +8 -15
  29. package/crates/naome-core/src/quality/config_support.rs +24 -0
  30. package/crates/naome-core/src/quality/mod.rs +72 -6
  31. package/crates/naome-core/src/quality/scanner/analysis/normalize.rs +78 -0
  32. package/crates/naome-core/src/quality/scanner/analysis.rs +160 -0
  33. package/crates/naome-core/src/quality/scanner/repo_paths.rs +39 -3
  34. package/crates/naome-core/src/quality/scanner.rs +200 -215
  35. package/crates/naome-core/src/quality/semantic/checks.rs +134 -0
  36. package/crates/naome-core/src/quality/semantic/extract.rs +158 -0
  37. package/crates/naome-core/src/quality/semantic/model.rs +85 -0
  38. package/crates/naome-core/src/quality/semantic/route.rs +52 -0
  39. package/crates/naome-core/src/quality/semantic.rs +68 -0
  40. package/crates/naome-core/src/quality/structure/adapters.rs +84 -0
  41. package/crates/naome-core/src/quality/structure/checks/basic.rs +153 -0
  42. package/crates/naome-core/src/quality/structure/checks/directory.rs +134 -0
  43. package/crates/naome-core/src/quality/structure/checks/pairing.rs +63 -0
  44. package/crates/naome-core/src/quality/structure/checks.rs +124 -0
  45. package/crates/naome-core/src/quality/structure/classify/roles.rs +188 -0
  46. package/crates/naome-core/src/quality/structure/classify.rs +146 -0
  47. package/crates/naome-core/src/quality/structure/config.rs +89 -0
  48. package/crates/naome-core/src/quality/structure/defaults.rs +107 -0
  49. package/crates/naome-core/src/quality/structure/mod.rs +77 -0
  50. package/crates/naome-core/src/quality/structure/model.rs +131 -0
  51. package/crates/naome-core/src/quality/types.rs +43 -2
  52. package/crates/naome-core/src/route/builtin_checks.rs +141 -0
  53. package/crates/naome-core/src/route/builtin_context.rs +73 -0
  54. package/crates/naome-core/src/route/builtin_integrity.rs +49 -0
  55. package/crates/naome-core/src/route/builtin_require.rs +40 -0
  56. package/crates/naome-core/src/route/context.rs +180 -0
  57. package/crates/naome-core/src/route/execution.rs +96 -0
  58. package/crates/naome-core/src/route/execution_baselines.rs +146 -0
  59. package/crates/naome-core/src/route/execution_support.rs +57 -0
  60. package/crates/naome-core/src/route/execution_tasks.rs +71 -0
  61. package/crates/naome-core/src/route/git_ops.rs +72 -0
  62. package/crates/naome-core/src/route/quality_gate.rs +73 -0
  63. package/crates/naome-core/src/route/quality_gate_config.rs +126 -0
  64. package/crates/naome-core/src/route/quality_gate_snapshot.rs +69 -0
  65. package/crates/naome-core/src/route/worktree.rs +75 -0
  66. package/crates/naome-core/src/route/worktree_files.rs +32 -0
  67. package/crates/naome-core/src/route/worktree_plan.rs +131 -0
  68. package/crates/naome-core/src/route.rs +44 -1217
  69. package/crates/naome-core/src/verification.rs +1 -0
  70. package/crates/naome-core/src/workflow/doctor.rs +144 -0
  71. package/crates/naome-core/src/workflow/mod.rs +2 -0
  72. package/crates/naome-core/src/workflow/mutation.rs +1 -2
  73. package/crates/naome-core/tests/decision.rs +24 -118
  74. package/crates/naome-core/tests/harness_health.rs +2 -0
  75. package/crates/naome-core/tests/install_plan.rs +2 -0
  76. package/crates/naome-core/tests/quality.rs +26 -123
  77. package/crates/naome-core/tests/quality_performance.rs +231 -0
  78. package/crates/naome-core/tests/quality_structure.rs +116 -0
  79. package/crates/naome-core/tests/quality_structure_adapters.rs +98 -0
  80. package/crates/naome-core/tests/quality_structure_policy.rs +144 -0
  81. package/crates/naome-core/tests/quality_structure_support/mod.rs +249 -0
  82. package/crates/naome-core/tests/repo_support/mod.rs +16 -0
  83. package/crates/naome-core/tests/repo_support/repo.rs +113 -0
  84. package/crates/naome-core/tests/repo_support/repo_factories.rs +99 -0
  85. package/crates/naome-core/tests/repo_support/repo_helpers.rs +123 -0
  86. package/crates/naome-core/tests/repo_support/routes.rs +81 -0
  87. package/crates/naome-core/tests/repo_support/verification.rs +168 -0
  88. package/crates/naome-core/tests/repo_support/verification_values.rs +135 -0
  89. package/crates/naome-core/tests/route.rs +1 -1376
  90. package/crates/naome-core/tests/route_baseline.rs +86 -0
  91. package/crates/naome-core/tests/route_completion.rs +141 -0
  92. package/crates/naome-core/tests/route_harness_refresh.rs +135 -0
  93. package/crates/naome-core/tests/route_user_diff.rs +202 -0
  94. package/crates/naome-core/tests/route_worktree.rs +54 -0
  95. package/crates/naome-core/tests/semantic_legacy.rs +140 -0
  96. package/crates/naome-core/tests/task_state.rs +60 -432
  97. package/crates/naome-core/tests/task_state_compact_support/repo.rs +1 -1
  98. package/crates/naome-core/tests/task_state_support/mod.rs +163 -0
  99. package/crates/naome-core/tests/task_state_support/states.rs +84 -0
  100. package/crates/naome-core/tests/verification.rs +4 -45
  101. package/crates/naome-core/tests/verification_contract.rs +22 -78
  102. package/crates/naome-core/tests/workflow_doctor.rs +24 -0
  103. package/crates/naome-core/tests/workflow_policy.rs +6 -1
  104. package/crates/naome-core/tests/workflow_support/mod.rs +1 -1
  105. package/installer/agents.js +90 -0
  106. package/installer/context.js +67 -0
  107. package/installer/filesystem.js +166 -0
  108. package/installer/flows.js +84 -0
  109. package/installer/git-boundary.js +171 -0
  110. package/installer/git-hook-content.js +36 -0
  111. package/installer/git-hooks.js +134 -0
  112. package/installer/git-local.js +2 -0
  113. package/installer/git-shared.js +35 -0
  114. package/installer/harness-file-ops.js +140 -0
  115. package/installer/harness-files.js +56 -0
  116. package/installer/harness-verification.js +123 -0
  117. package/installer/install-plan.js +66 -0
  118. package/installer/main.js +25 -0
  119. package/installer/manifest-state.js +167 -0
  120. package/installer/native-build.js +24 -0
  121. package/installer/native-format.js +6 -0
  122. package/installer/native.js +162 -0
  123. package/installer/output.js +131 -0
  124. package/installer/version.js +32 -0
  125. package/native/darwin-arm64/naome +0 -0
  126. package/native/linux-x64/naome +0 -0
  127. package/package.json +2 -1
  128. package/templates/naome-root/.naome/bin/check-harness-health.js +3 -3
  129. package/templates/naome-root/.naome/bin/check-task-state.js +3 -3
  130. package/templates/naome-root/.naome/bin/naome.js +32 -21
  131. package/templates/naome-root/.naome/manifest.json +5 -3
  132. package/templates/naome-root/.naome/repository-structure.json +90 -0
  133. package/templates/naome-root/.naome/verification.json +1 -0
  134. package/templates/naome-root/.naomeignore +1 -0
  135. package/templates/naome-root/docs/naome/agent-workflow.md +16 -14
  136. package/templates/naome-root/docs/naome/index.md +4 -3
  137. package/templates/naome-root/docs/naome/repository-quality.md +66 -4
  138. package/templates/naome-root/docs/naome/repository-structure.md +51 -0
  139. package/templates/naome-root/docs/naome/testing.md +2 -1
package/bin/naome.js CHANGED
@@ -12,7 +12,7 @@ const packageVersion = packageMetadata.version;
12
12
  const nativeBinaryName = process.platform === "win32" ? "naome.exe" : "naome";
13
13
  const args = process.argv.slice(2);
14
14
  const [command] = args;
15
- const helpCommands = "status [--json]|next [--json]|intent --prompt-file <path> [--json]|intent --prompt <text> [--json]|route --prompt-file <path> [--execute] [--json]|route --prompt <text> [--execute] [--json]|explain --prompt-file <path> [--json]|explain --prompt <text> [--json]|install|sync [--check-update]|update [--json] [--execute]|quality init [--json]|quality check --changed [--json]|quality report [--json]|cleanup plan [--json]|cleanup route --path <path> [--json]|refresh-integrity [--json]|workflow search-profile|check-search|phases|processes|mutations [--json]|commit -m \"type(scope): message\"".split("|");
15
+ const helpCommands = "status [--json]|next [--json]|intent --prompt-file <path> [--json]|intent --prompt <text> [--json]|route --prompt-file <path> [--execute] [--json]|route --prompt <text> [--execute] [--json]|explain --prompt-file <path> [--json]|explain --prompt <text> [--json]|doctor [--json]|install|sync [--check-update]|update [--json] [--execute]|quality init [--baseline|--deep-baseline] [--json]|quality check --changed [--include-scanned-paths] [--json]|quality report [--deep] [--include-scanned-paths] [--json]|quality cache status [--json]|quality cache clear|semantic report [--deep] [--json]|semantic check --changed [--json]|semantic route --finding <id> [--json]|semantic loop [--json]|structure report [--json]|structure explain --path <path> [--json]|cleanup plan [--json]|cleanup route --path <path> [--json]|refresh-integrity [--json]|workflow search-profile|check-search|phases|processes|mutations [--json]|commit -m \"type(scope): message\"".split("|");
16
16
 
17
17
  if (isHelpRequest(args)) {
18
18
  printHelp();
@@ -265,13 +265,19 @@ function runNativePackageCommand(args) {
265
265
 
266
266
  function ensureRepositoryQualityInitialized(nativeBinary, qualityConfigExisted) {
267
267
  const root = process.cwd();
268
- if (qualityConfigExisted || !existsSync(join(root, ".naome"))) {
268
+ if (!existsSync(join(root, ".naome"))) {
269
269
  return;
270
270
  }
271
271
 
272
- for (const path of repositoryQualityPaths(root)) {
273
- if (existsSync(path)) {
274
- unlinkSync(path);
272
+ if (qualityConfigExisted && repositoryQualitySupportFilesExist(root)) {
273
+ return;
274
+ }
275
+
276
+ if (!qualityConfigExisted) {
277
+ for (const path of repositoryQualityPaths(root)) {
278
+ if (existsSync(path)) {
279
+ unlinkSync(path);
280
+ }
275
281
  }
276
282
  }
277
283
 
@@ -294,6 +300,21 @@ function ensureRepositoryQualityInitialized(nativeBinary, qualityConfigExisted)
294
300
  }
295
301
  process.exit(result.status === null ? 1 : result.status);
296
302
  }
303
+
304
+ const output = result.stdout.trim();
305
+ if (output) {
306
+ try {
307
+ const init = JSON.parse(output);
308
+ if (init.configWritten || init.structureConfigWritten) {
309
+ console.log("repository quality policy initialized");
310
+ }
311
+ if (init.baselinePending) {
312
+ console.log("baseline pending: run naome quality init --baseline");
313
+ }
314
+ } catch (_error) {
315
+ console.log(output);
316
+ }
317
+ }
297
318
  }
298
319
 
299
320
  function repositoryQualityConfigPath(root) {
@@ -303,10 +324,18 @@ function repositoryQualityConfigPath(root) {
303
324
  function repositoryQualityPaths(root) {
304
325
  return [
305
326
  repositoryQualityConfigPath(root),
327
+ join(root, ".naome", "repository-structure.json"),
306
328
  join(root, ".naome", "repository-quality-baseline.json")
307
329
  ];
308
330
  }
309
331
 
332
+ function repositoryQualitySupportFilesExist(root) {
333
+ return [
334
+ join(root, ".naome", "repository-structure.json"),
335
+ join(root, ".naome", "repository-quality-baseline.json")
336
+ ].every((path) => existsSync(path));
337
+ }
338
+
310
339
  function resolveNativePackageBinary() {
311
340
  const candidates = [
312
341
  process.env.NAOME_NATIVE_BIN && resolve(process.cwd(), process.env.NAOME_NATIVE_BIN),
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-cli"
3
- version = "1.2.0"
3
+ version = "1.3.0"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -3,11 +3,13 @@ use std::path::Path;
3
3
  use crate::check_commands::{run_harness_health, run_task_state, run_verification_contract};
4
4
  use crate::install_bridge::run_install_bridge;
5
5
  use crate::prompt_commands::{run_explain, run_intent, run_route};
6
- use crate::quality_commands::{run_cleanup_command, run_quality_command};
6
+ use crate::quality_commands::{
7
+ run_cleanup_command, run_quality_command, run_semantic_command, run_structure_command,
8
+ };
7
9
  use crate::simple_commands::{
8
10
  print_install_plan, run_commit_paths, run_journal_task, seed_verification,
9
11
  };
10
- use crate::workflow_commands::{run_refresh_integrity, run_workflow_command};
12
+ use crate::workflow_commands::{run_doctor, run_refresh_integrity, run_workflow_command};
11
13
 
12
14
  pub fn dispatch_command(
13
15
  root: &Path,
@@ -19,7 +21,10 @@ pub fn dispatch_command(
19
21
  "install-plan" => print_install_plan(args)?,
20
22
  "seed-verification" => seed_verification(root)?,
21
23
  "refresh-integrity" => run_refresh_integrity(root, args)?,
24
+ "doctor" => run_doctor(root, args)?,
22
25
  "quality" => run_quality_command(root, args)?,
26
+ "semantic" => run_semantic_command(root, args)?,
27
+ "structure" => run_structure_command(root, args)?,
23
28
  "cleanup" => run_cleanup_command(root, args)?,
24
29
  "workflow" => run_workflow_command(root, args)?,
25
30
  "check-harness-health" => run_harness_health(root, args)?,
@@ -22,14 +22,23 @@ const HELP: &str = r#"Usage:
22
22
  naome route --prompt <text> [--execute] [--json]
23
23
  naome explain --prompt-file <path> [--json]
24
24
  naome explain --prompt <text> [--json]
25
+ naome doctor [--json]
25
26
  naome install [--package-root <path>] [--installer-js <path>]
26
27
  naome sync [--package-root <path>] [--installer-js <path>]
27
28
  naome install-plan [--harness-version <version>]
28
29
  naome seed-verification
29
30
  naome refresh-integrity [--root <path>] [--json]
30
- naome quality init [--json]
31
- naome quality check --changed [--json]
32
- naome quality report [--json]
31
+ naome quality init [--baseline|--deep-baseline] [--json]
32
+ naome quality check --changed [--include-scanned-paths] [--json]
33
+ naome quality report [--deep] [--include-scanned-paths] [--json]
34
+ naome quality cache status [--json]
35
+ naome quality cache clear
36
+ naome semantic report [--deep] [--json]
37
+ naome semantic check --changed [--json]
38
+ naome semantic route --finding <id> [--json]
39
+ naome semantic loop [--json]
40
+ naome structure report [--json]
41
+ naome structure explain --path <path> [--json]
33
42
  naome cleanup plan [--json]
34
43
  naome cleanup route --path <path> [--json]
35
44
  naome workflow search-profile [--json]
@@ -41,6 +50,30 @@ const HELP: &str = r#"Usage:
41
50
  naome check-task-state [--root <path>] [--admission|--progress|--commit-gate|--push-gate] [--allow-missing-archive]
42
51
  naome validate-verification [--root <path>]"#;
43
52
 
53
+ const PUBLIC_COMMANDS: &[&str] = &[
54
+ "status",
55
+ "next",
56
+ "intent",
57
+ "route",
58
+ "explain",
59
+ "doctor",
60
+ "journal-task",
61
+ "commit-paths",
62
+ "seed-verification",
63
+ "refresh-integrity",
64
+ "workflow",
65
+ "quality",
66
+ "semantic",
67
+ "structure",
68
+ "cleanup",
69
+ "install-plan",
70
+ "install",
71
+ "sync",
72
+ "check-harness-health",
73
+ "check-task-state",
74
+ "validate-verification",
75
+ ];
76
+
44
77
  fn main() {
45
78
  if let Err(error) = run() {
46
79
  eprintln!("NAOME: {error}");
@@ -60,25 +93,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
60
93
  return Ok(());
61
94
  }
62
95
 
63
- if command != "status"
64
- && command != "next"
65
- && command != "intent"
66
- && command != "route"
67
- && command != "explain"
68
- && command != "journal-task"
69
- && command != "commit-paths"
70
- && command != "seed-verification"
71
- && command != "refresh-integrity"
72
- && command != "workflow"
73
- && command != "quality"
74
- && command != "cleanup"
75
- && command != "install-plan"
76
- && command != "install"
77
- && command != "sync"
78
- && command != "check-harness-health"
79
- && command != "check-task-state"
80
- && command != "validate-verification"
81
- {
96
+ if !PUBLIC_COMMANDS.contains(&command) {
82
97
  print_help();
83
98
  return Err(format!("unknown command: {command}").into());
84
99
  }
@@ -1,7 +1,9 @@
1
1
  use std::path::Path;
2
2
 
3
3
  use naome_core::{
4
- check_repository_quality, init_repository_quality, plan_quality_cleanup, route_quality_cleanup,
4
+ check_repository_quality, check_semantic_legacy, clear_quality_cache,
5
+ explain_repository_structure, init_repository_quality_with_mode, plan_quality_cleanup,
6
+ quality_cache_status, route_quality_cleanup, semantic_route_for_finding, QualityInitMode,
5
7
  QualityMode,
6
8
  };
7
9
 
@@ -9,22 +11,28 @@ use crate::cli_args::option_value;
9
11
 
10
12
  pub fn run_quality_command(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
11
13
  let Some(subcommand) = args.get(1).map(String::as_str) else {
12
- return Err("naome quality requires init, check, or report.".into());
14
+ return Err("naome quality requires init, check, report, or cache.".into());
13
15
  };
14
16
  let json = args.iter().any(|arg| arg == "--json");
15
17
 
16
18
  match subcommand {
17
19
  "init" => {
18
- let result = init_repository_quality(root)?;
20
+ let result = init_repository_quality_with_mode(root, quality_init_mode(args)?)?;
19
21
  if json {
20
22
  println!("{}", serde_json::to_string_pretty(&result)?);
21
23
  } else {
22
24
  println!("NAOME repository quality initialized.");
23
- println!("Baseline violations: {}", result.baseline_violations);
25
+ if result.baseline_pending {
26
+ println!("repository quality policy initialized");
27
+ println!("baseline pending: run naome quality init --baseline");
28
+ } else {
29
+ println!("Baseline violations: {}", result.baseline_violations);
30
+ }
24
31
  }
25
32
  }
26
33
  "check" => run_quality_check(root, args, json)?,
27
- "report" => run_quality_report(root, json)?,
34
+ "report" => run_quality_report(root, args, json)?,
35
+ "cache" => run_quality_cache(root, args, json)?,
28
36
  _ => return Err(format!("unknown naome quality command: {subcommand}").into()),
29
37
  }
30
38
  Ok(())
@@ -44,6 +52,42 @@ pub fn run_cleanup_command(root: &Path, args: &[String]) -> Result<(), Box<dyn s
44
52
  Ok(())
45
53
  }
46
54
 
55
+ pub fn run_structure_command(
56
+ root: &Path,
57
+ args: &[String],
58
+ ) -> Result<(), Box<dyn std::error::Error>> {
59
+ let Some(subcommand) = args.get(1).map(String::as_str) else {
60
+ return Err("naome structure requires report or explain.".into());
61
+ };
62
+ let json = args.iter().any(|arg| arg == "--json");
63
+
64
+ match subcommand {
65
+ "report" => run_structure_report(root, json)?,
66
+ "explain" => run_structure_explain(root, args, json)?,
67
+ _ => return Err(format!("unknown naome structure command: {subcommand}").into()),
68
+ }
69
+ Ok(())
70
+ }
71
+
72
+ pub fn run_semantic_command(
73
+ root: &Path,
74
+ args: &[String],
75
+ ) -> Result<(), Box<dyn std::error::Error>> {
76
+ let Some(subcommand) = args.get(1).map(String::as_str) else {
77
+ return Err("naome semantic requires report, check, route, or loop.".into());
78
+ };
79
+ let json = args.iter().any(|arg| arg == "--json");
80
+
81
+ match subcommand {
82
+ "report" => run_semantic_report(root, args, json)?,
83
+ "check" => run_semantic_check(root, args, json)?,
84
+ "route" => run_semantic_route(root, args, json)?,
85
+ "loop" => run_semantic_loop(root, json)?,
86
+ _ => return Err(format!("unknown naome semantic command: {subcommand}").into()),
87
+ }
88
+ Ok(())
89
+ }
90
+
47
91
  fn run_quality_check(
48
92
  root: &Path,
49
93
  args: &[String],
@@ -52,9 +96,9 @@ fn run_quality_check(
52
96
  if !args.iter().any(|arg| arg == "--changed") {
53
97
  return Err("naome quality check requires --changed.".into());
54
98
  }
55
- let report = check_repository_quality(root, QualityMode::Changed)?;
99
+ let report = check_repository_quality(root, QualityMode::ChangedFast)?;
56
100
  if json {
57
- println!("{}", serde_json::to_string_pretty(&report)?);
101
+ println!("{}", report_json(&report, args)?);
58
102
  } else if report.ok {
59
103
  println!("NAOME repository quality OK.");
60
104
  } else {
@@ -72,10 +116,19 @@ fn run_quality_check(
72
116
  Ok(())
73
117
  }
74
118
 
75
- fn run_quality_report(root: &Path, json: bool) -> Result<(), Box<dyn std::error::Error>> {
76
- let report = check_repository_quality(root, QualityMode::Report)?;
119
+ fn run_quality_report(
120
+ root: &Path,
121
+ args: &[String],
122
+ json: bool,
123
+ ) -> Result<(), Box<dyn std::error::Error>> {
124
+ let mode = if args.iter().any(|arg| arg == "--deep") {
125
+ QualityMode::DeepReport
126
+ } else {
127
+ QualityMode::Report
128
+ };
129
+ let report = check_repository_quality(root, mode)?;
77
130
  if json {
78
- println!("{}", serde_json::to_string_pretty(&report)?);
131
+ println!("{}", report_json(&report, args)?);
79
132
  } else if report.violations.is_empty() {
80
133
  println!("NAOME repository quality report: no debt found.");
81
134
  } else {
@@ -91,6 +144,32 @@ fn run_quality_report(root: &Path, json: bool) -> Result<(), Box<dyn std::error:
91
144
  Ok(())
92
145
  }
93
146
 
147
+ fn run_quality_cache(
148
+ root: &Path,
149
+ args: &[String],
150
+ json: bool,
151
+ ) -> Result<(), Box<dyn std::error::Error>> {
152
+ let Some(action) = args.get(2).map(String::as_str) else {
153
+ return Err("naome quality cache requires status or clear.".into());
154
+ };
155
+ let status = match action {
156
+ "status" => quality_cache_status(root)?,
157
+ "clear" => clear_quality_cache(root)?,
158
+ _ => return Err(format!("unknown naome quality cache command: {action}").into()),
159
+ };
160
+ if json {
161
+ println!("{}", serde_json::to_string_pretty(&status)?);
162
+ } else if action == "clear" {
163
+ println!("NAOME quality cache cleared.");
164
+ } else {
165
+ println!(
166
+ "NAOME quality cache: {} entrie(s), {} byte(s).",
167
+ status.entry_count, status.bytes
168
+ );
169
+ }
170
+ Ok(())
171
+ }
172
+
94
173
  fn run_cleanup_plan(root: &Path, json: bool) -> Result<(), Box<dyn std::error::Error>> {
95
174
  let plan = plan_quality_cleanup(root)?;
96
175
  if json {
@@ -132,6 +211,234 @@ fn run_cleanup_route(
132
211
  Ok(())
133
212
  }
134
213
 
214
+ fn run_structure_report(root: &Path, json: bool) -> Result<(), Box<dyn std::error::Error>> {
215
+ let mut report = check_repository_quality(root, QualityMode::Report)?;
216
+ report
217
+ .violations
218
+ .retain(|violation| is_structure_check(&violation.check_id));
219
+ report.summary.violation_count = report.violations.len();
220
+ report.summary.blocking_violation_count = report.violations.len();
221
+ report.summary.baseline_violation_count = report
222
+ .violations
223
+ .iter()
224
+ .filter(|violation| violation.baseline)
225
+ .count();
226
+ report.ok = report.violations.is_empty();
227
+ report.schema = "naome.repository-structure-report.v1".to_string();
228
+
229
+ if json {
230
+ println!("{}", serde_json::to_string_pretty(&report)?);
231
+ } else if report.violations.is_empty() {
232
+ println!("NAOME repository structure report: no debt found.");
233
+ } else {
234
+ println!(
235
+ "NAOME repository structure report: {} violation(s).",
236
+ report.violations.len()
237
+ );
238
+ for violation in report.violations.iter().take(20) {
239
+ print_quality_violation(violation);
240
+ }
241
+ }
242
+ Ok(())
243
+ }
244
+
245
+ fn run_structure_explain(
246
+ root: &Path,
247
+ args: &[String],
248
+ json: bool,
249
+ ) -> Result<(), Box<dyn std::error::Error>> {
250
+ let Some(path) = option_value(args, "--path") else {
251
+ return Err("naome structure explain requires --path <path>.".into());
252
+ };
253
+ let explanation = explain_repository_structure(root, path)?;
254
+ if json {
255
+ println!("{}", serde_json::to_string_pretty(&explanation)?);
256
+ } else {
257
+ println!(
258
+ "{} role={} layer={} directory={}",
259
+ explanation.path, explanation.role, explanation.layer, explanation.directory
260
+ );
261
+ if let Some(language) = explanation.language {
262
+ println!("language={language}");
263
+ }
264
+ if let Some(module) = explanation.module {
265
+ println!("module={module}");
266
+ }
267
+ }
268
+ Ok(())
269
+ }
270
+
271
+ fn run_semantic_report(
272
+ root: &Path,
273
+ args: &[String],
274
+ json: bool,
275
+ ) -> Result<(), Box<dyn std::error::Error>> {
276
+ let mode = if args.iter().any(|arg| arg == "--deep") {
277
+ QualityMode::DeepReport
278
+ } else {
279
+ QualityMode::Report
280
+ };
281
+ let report = check_semantic_legacy(root, mode)?;
282
+ if json {
283
+ println!("{}", serde_json::to_string_pretty(&report)?);
284
+ } else if report.findings.is_empty() {
285
+ println!("NAOME semantic legacy report: no cleanup candidates found.");
286
+ } else {
287
+ println!(
288
+ "NAOME semantic legacy report: {} finding(s).",
289
+ report.findings.len()
290
+ );
291
+ for finding in report.findings.iter().take(20) {
292
+ println!(
293
+ "- {} {}: {}",
294
+ finding.kind, finding.occurrences[0].path, finding.summary
295
+ );
296
+ }
297
+ }
298
+ Ok(())
299
+ }
300
+
301
+ fn run_semantic_check(
302
+ root: &Path,
303
+ args: &[String],
304
+ json: bool,
305
+ ) -> Result<(), Box<dyn std::error::Error>> {
306
+ if !args.iter().any(|arg| arg == "--changed") {
307
+ return Err("naome semantic check requires --changed.".into());
308
+ }
309
+ let report = check_semantic_legacy(root, QualityMode::ChangedFast)?;
310
+ if json {
311
+ println!("{}", serde_json::to_string_pretty(&report)?);
312
+ } else if report.ok {
313
+ println!("NAOME semantic legacy check OK.");
314
+ } else {
315
+ eprintln!(
316
+ "NAOME semantic legacy check failed with {} finding(s).",
317
+ report.findings.len()
318
+ );
319
+ for finding in report.findings.iter().take(20) {
320
+ eprintln!(
321
+ "- {} {}: {}",
322
+ finding.kind, finding.occurrences[0].path, finding.summary
323
+ );
324
+ }
325
+ }
326
+ if !report.ok {
327
+ std::process::exit(1);
328
+ }
329
+ Ok(())
330
+ }
331
+
332
+ fn run_semantic_route(
333
+ root: &Path,
334
+ args: &[String],
335
+ json: bool,
336
+ ) -> Result<(), Box<dyn std::error::Error>> {
337
+ let Some(finding_id) = option_value(args, "--finding") else {
338
+ return Err("naome semantic route requires --finding <id>.".into());
339
+ };
340
+ let report = check_semantic_legacy(root, QualityMode::DeepReport)?;
341
+ let Some(finding) = semantic_route_for_finding(&report, &finding_id) else {
342
+ return Err(format!("semantic finding not found: {finding_id}").into());
343
+ };
344
+ if json {
345
+ println!("{}", serde_json::to_string_pretty(&finding)?);
346
+ } else {
347
+ println!("NAOME semantic cleanup route for {}", finding.id);
348
+ println!("{}", finding.cleanup_route.intent);
349
+ for instruction in &finding.cleanup_route.agent_instructions {
350
+ println!("- {instruction}");
351
+ }
352
+ let paths = finding
353
+ .occurrences
354
+ .iter()
355
+ .map(|occurrence| occurrence.path.as_str())
356
+ .collect::<Vec<_>>()
357
+ .join(", ");
358
+ println!("Affected paths: {paths}");
359
+ }
360
+ Ok(())
361
+ }
362
+
363
+ fn run_semantic_loop(root: &Path, json: bool) -> Result<(), Box<dyn std::error::Error>> {
364
+ let changed = check_semantic_legacy(root, QualityMode::ChangedFast)?;
365
+ let (action, finding) = if let Some(finding) = changed.findings.first().cloned() {
366
+ ("cleanup_changed_finding", Some(finding))
367
+ } else {
368
+ let report = check_semantic_legacy(root, QualityMode::Report)?;
369
+ if let Some(finding) = report.findings.first().cloned() {
370
+ ("report_legacy_debt", Some(finding))
371
+ } else {
372
+ ("complete", None)
373
+ }
374
+ };
375
+
376
+ if json {
377
+ println!(
378
+ "{}",
379
+ serde_json::to_string_pretty(&serde_json::json!({
380
+ "schema": "naome.semantic-loop.v1",
381
+ "ok": action != "cleanup_changed_finding",
382
+ "action": action,
383
+ "finding": finding
384
+ }))?
385
+ );
386
+ } else {
387
+ match finding {
388
+ Some(finding) => {
389
+ println!("NAOME semantic loop action: {action}");
390
+ println!("{} {}", finding.kind, finding.id);
391
+ println!("{}", finding.summary);
392
+ println!("Run: naome semantic route --finding {} --json", finding.id);
393
+ }
394
+ None => println!("NAOME semantic loop complete: no semantic cleanup candidates found."),
395
+ }
396
+ }
397
+ Ok(())
398
+ }
399
+
400
+ fn is_structure_check(check_id: &str) -> bool {
401
+ matches!(
402
+ check_id,
403
+ "directory-role-mixing"
404
+ | "misplaced-file-role"
405
+ | "root-file-sprawl"
406
+ | "dumping-ground-directory"
407
+ | "directory-size"
408
+ | "path-depth"
409
+ | "case-collision"
410
+ | "test-source-pairing"
411
+ )
412
+ }
413
+
414
+ fn quality_init_mode(args: &[String]) -> Result<QualityInitMode, Box<dyn std::error::Error>> {
415
+ let baseline = args.iter().any(|arg| arg == "--baseline");
416
+ let deep_baseline = args.iter().any(|arg| arg == "--deep-baseline");
417
+ if baseline && deep_baseline {
418
+ return Err("naome quality init accepts only one baseline mode.".into());
419
+ }
420
+ Ok(if deep_baseline {
421
+ QualityInitMode::DeepBaseline
422
+ } else if baseline {
423
+ QualityInitMode::Baseline
424
+ } else {
425
+ QualityInitMode::SeedOnly
426
+ })
427
+ }
428
+
429
+ fn report_json(
430
+ report: &naome_core::QualityReport,
431
+ args: &[String],
432
+ ) -> Result<String, Box<dyn std::error::Error>> {
433
+ let mut value = serde_json::to_value(report)?;
434
+ if !args.iter().any(|arg| arg == "--include-scanned-paths") {
435
+ if let Some(object) = value.as_object_mut() {
436
+ object.remove("scannedPaths");
437
+ }
438
+ }
439
+ Ok(serde_json::to_string_pretty(&value)?)
440
+ }
441
+
135
442
  fn print_quality_violation(violation: &naome_core::QualityViolation) {
136
443
  let location = violation
137
444
  .line
@@ -1,7 +1,7 @@
1
1
  use std::path::Path;
2
2
 
3
3
  use naome_core::{
4
- classify_mutations, refresh_integrity, safe_rg_args, tracked_process_report,
4
+ classify_mutations, doctor_report, refresh_integrity, safe_rg_args, tracked_process_report,
5
5
  validate_search_command, verification_phase_plan,
6
6
  };
7
7
 
@@ -83,6 +83,26 @@ pub fn run_workflow_command(
83
83
  Ok(())
84
84
  }
85
85
 
86
+ pub fn run_doctor(root: &Path, args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
87
+ let report = doctor_report(root)?;
88
+ if args.iter().any(|arg| arg == "--json") {
89
+ println!("{}", serde_json::to_string_pretty(&report)?);
90
+ } else {
91
+ println!(
92
+ "NAOME doctor: {}",
93
+ if report.ok { "ok" } else { "attention needed" }
94
+ );
95
+ println!("{}", report.next_action);
96
+ if !report.recommended_check_ids.is_empty() {
97
+ println!(
98
+ "Recommended checks: {}",
99
+ report.recommended_check_ids.join(", ")
100
+ );
101
+ }
102
+ }
103
+ Ok(())
104
+ }
105
+
86
106
  fn run_check_search(
87
107
  root: &Path,
88
108
  args: &[String],
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-core"
3
- version = "1.2.0"
3
+ version = "1.3.0"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -0,0 +1,64 @@
1
+ use std::ffi::OsString;
2
+ use std::path::Path;
3
+ use std::process::Command;
4
+
5
+ use crate::models::{CheckDecision, NaomeError};
6
+
7
+ pub(super) fn run_node_check(
8
+ root: &Path,
9
+ script: &str,
10
+ args: &[&str],
11
+ ) -> Result<CheckDecision, NaomeError> {
12
+ let mut command_args = vec![script.to_string()];
13
+ command_args.extend(args.iter().map(ToString::to_string));
14
+ let node_bin = std::env::var_os("NAOME_NODE_BIN").unwrap_or_else(|| OsString::from("node"));
15
+ let output = Command::new(&node_bin)
16
+ .args(&command_args)
17
+ .current_dir(root)
18
+ .output()?;
19
+ let mut combined = String::new();
20
+ combined.push_str(&String::from_utf8_lossy(&output.stdout));
21
+ combined.push_str(&String::from_utf8_lossy(&output.stderr));
22
+
23
+ Ok(CheckDecision {
24
+ command: format!(
25
+ "{} {}{}",
26
+ node_bin.to_string_lossy(),
27
+ script,
28
+ if args.is_empty() {
29
+ String::new()
30
+ } else {
31
+ format!(" {}", args.join(" "))
32
+ }
33
+ ),
34
+ exit_code: output.status.code(),
35
+ ok: output.status.success(),
36
+ output: combined.trim().to_string(),
37
+ })
38
+ }
39
+
40
+ pub(super) fn extract_known_actions(output: &str) -> Vec<&'static str> {
41
+ const KNOWN: [&str; 11] = [
42
+ "commit_task_baseline",
43
+ "review_task_diff",
44
+ "request_task_changes",
45
+ "cancel_task_changes",
46
+ "commit_upgrade_baseline",
47
+ "review_diff_first",
48
+ "cancel_upgrade_baseline",
49
+ "run_first_run_protocol",
50
+ "run_upgrade_protocol",
51
+ "review_unowned_diff",
52
+ "create_task",
53
+ ];
54
+
55
+ let actions: Vec<&'static str> = KNOWN
56
+ .into_iter()
57
+ .filter(|action| output.contains(action))
58
+ .collect();
59
+ if actions.is_empty() {
60
+ vec!["review_task_admission"]
61
+ } else {
62
+ actions
63
+ }
64
+ }