@noir-ai/cli 1.17.0-beta.1 → 1.18.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1999,7 +1999,8 @@ function hasScannableLayout(root) {
1999
1999
  }
2000
2000
  function addHygieneFile(rel, walk) {
2001
2001
  const name = rel.slice(rel.lastIndexOf("/") + 1);
2002
- if (name.startsWith(".") || HYGIENE_EXCLUDED_FILES.has(name)) return;
2002
+ if (name.startsWith(".") || HYGIENE_EXCLUDED_FILES.has(name) || HYGIENE_EXCLUDED_PATHS.has(rel))
2003
+ return;
2003
2004
  const kind = hygieneKindOf(rel);
2004
2005
  if (kind === null) return;
2005
2006
  if (walk.files.length >= HYGIENE_MAX_FILES) {
@@ -2117,7 +2118,7 @@ function scanOutputHygiene(root) {
2117
2118
  warn: findings.length - fail2
2118
2119
  };
2119
2120
  }
2120
- var HYGIENE_FINDING_CAP, HYGIENE_MAX_FILE_BYTES, HYGIENE_MAX_FILES, HYGIENE_CODE_EXTENSIONS, HYGIENE_MARKDOWN_EXTENSIONS, HYGIENE_SKIP_DIRS, HYGIENE_EXCLUDED_DIRS, HYGIENE_EXCLUDED_FILES, NO_SKIP_DIRS;
2121
+ var HYGIENE_FINDING_CAP, HYGIENE_MAX_FILE_BYTES, HYGIENE_MAX_FILES, HYGIENE_CODE_EXTENSIONS, HYGIENE_MARKDOWN_EXTENSIONS, HYGIENE_SKIP_DIRS, HYGIENE_EXCLUDED_DIRS, HYGIENE_EXCLUDED_FILES, HYGIENE_EXCLUDED_PATHS, NO_SKIP_DIRS;
2121
2122
  var init_hygiene_scan = __esm({
2122
2123
  "src/hygiene-scan.ts"() {
2123
2124
  "use strict";
@@ -2129,6 +2130,14 @@ var init_hygiene_scan = __esm({
2129
2130
  HYGIENE_SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
2130
2131
  HYGIENE_EXCLUDED_DIRS = /* @__PURE__ */ new Set(["docs/internal", "docs/decisions", "docs/roadmap"]);
2131
2132
  HYGIENE_EXCLUDED_FILES = /* @__PURE__ */ new Set(["CHANGELOG.md"]);
2133
+ HYGIENE_EXCLUDED_PATHS = /* @__PURE__ */ new Set([
2134
+ "docs/README.md",
2135
+ "docs/reference/cli.md",
2136
+ "docs/reference/config.md",
2137
+ "docs/reference/mcp-tools.md",
2138
+ "docs/reference/packages.md",
2139
+ "docs/reference/skills.md"
2140
+ ]);
2132
2141
  NO_SKIP_DIRS = /* @__PURE__ */ new Set();
2133
2142
  }
2134
2143
  });
@@ -4444,6 +4453,7 @@ function collectLeaves(cmd, path, out) {
4444
4453
  const isLeaf = hasAction(cmd) && children.length === 0;
4445
4454
  if (isLeaf) {
4446
4455
  const argv = path;
4456
+ if (INTERNAL_LEAF_COMMANDS.has(argv.join(" "))) return;
4447
4457
  const id = argv.join(" ");
4448
4458
  const description = cmd.description() || argv.join(" ");
4449
4459
  out.push({
@@ -4467,7 +4477,7 @@ function buildPaletteCommands(program2) {
4467
4477
  collectLeaves(program2, [], out);
4468
4478
  return out;
4469
4479
  }
4470
- var DESTRUCTIVE_PREFIXES;
4480
+ var DESTRUCTIVE_PREFIXES, INTERNAL_LEAF_COMMANDS;
4471
4481
  var init_registry = __esm({
4472
4482
  "src/tui/commands/registry.ts"() {
4473
4483
  "use strict";
@@ -4489,6 +4499,7 @@ var init_registry = __esm({
4489
4499
  ["update"],
4490
4500
  ["run"]
4491
4501
  ];
4502
+ INTERNAL_LEAF_COMMANDS = /* @__PURE__ */ new Set(["hook"]);
4492
4503
  }
4493
4504
  });
4494
4505
 
@@ -4948,8 +4959,55 @@ var init_home = __esm({
4948
4959
  }
4949
4960
  });
4950
4961
 
4962
+ // src/commands/hook.ts
4963
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
4964
+ import { join as join5 } from "path";
4965
+ function hook() {
4966
+ let eventName = "SessionStart";
4967
+ let root = process.cwd();
4968
+ try {
4969
+ const input = JSON.parse(readFileSync4(0, "utf8"));
4970
+ if (typeof input.hook_event_name === "string" && input.hook_event_name !== "") {
4971
+ eventName = input.hook_event_name;
4972
+ }
4973
+ if (typeof input.cwd === "string" && input.cwd !== "") root = input.cwd;
4974
+ } catch {
4975
+ }
4976
+ process.stdout.write(JSON.stringify(hookOutput(eventName, root)));
4977
+ }
4978
+ function hookOutput(eventName, root) {
4979
+ let routerPath = null;
4980
+ let rulesPath = null;
4981
+ for (let dir = root; ; dir = join5(dir, "..")) {
4982
+ const router = join5(dir, ".noir", "router.md");
4983
+ const rules = join5(dir, ".noir", "rules", "anti-slop.md");
4984
+ if (existsSync3(router) || existsSync3(rules)) {
4985
+ routerPath = router;
4986
+ rulesPath = rules;
4987
+ break;
4988
+ }
4989
+ const parent = join5(dir, "..");
4990
+ if (parent === dir) break;
4991
+ }
4992
+ const parts = [];
4993
+ for (const path of [routerPath, rulesPath]) {
4994
+ if (path === null) continue;
4995
+ try {
4996
+ const text = readFileSync4(path, "utf8").trim();
4997
+ if (text !== "") parts.push(text);
4998
+ } catch {
4999
+ }
5000
+ }
5001
+ return parts.length === 0 ? { hookSpecificOutput: { hookEventName: eventName } } : { hookSpecificOutput: { hookEventName: eventName, additionalContext: parts.join("\n\n") } };
5002
+ }
5003
+ var init_hook = __esm({
5004
+ "src/commands/hook.ts"() {
5005
+ "use strict";
5006
+ }
5007
+ });
5008
+
4951
5009
  // src/commands/memory.ts
4952
- import { readFileSync as readFileSync4 } from "fs";
5010
+ import { readFileSync as readFileSync5 } from "fs";
4953
5011
  import { loadProjectInfo as loadProjectInfo13 } from "@noir-ai/core";
4954
5012
  function resolveWorkspace() {
4955
5013
  const routing = workspaceRoutingTarget(process.cwd());
@@ -5167,14 +5225,14 @@ async function resolveCaptureContent(opts) {
5167
5225
  if (typeof opts.content === "string" && opts.content.length > 0) return opts.content;
5168
5226
  if (typeof opts.file === "string" && opts.file.length > 0) {
5169
5227
  try {
5170
- return readFileSync4(opts.file, "utf8");
5228
+ return readFileSync5(opts.file, "utf8");
5171
5229
  } catch {
5172
5230
  fail(EXIT.USAGE, `memory capture could not read --file ${opts.file}`, opts);
5173
5231
  }
5174
5232
  }
5175
5233
  if (!process.stdin.isTTY) {
5176
5234
  try {
5177
- return readFileSync4(0, "utf8");
5235
+ return readFileSync5(0, "utf8");
5178
5236
  } catch {
5179
5237
  fail(
5180
5238
  EXIT.USAGE,
@@ -6557,8 +6615,8 @@ var init_run_status = __esm({
6557
6615
  });
6558
6616
 
6559
6617
  // src/commands/run.ts
6560
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
6561
- import { join as join5 } from "path";
6618
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
6619
+ import { join as join6 } from "path";
6562
6620
  import { SUPPORTED_HOSTS } from "@noir-ai/adapters";
6563
6621
  import { isDeniedEnvKey as isDeniedEnvKey2, loadNoirEnv as loadNoirEnv3, NOIR_DIR as NOIR_DIR2, parseConfig, paths as paths2 } from "@noir-ai/core";
6564
6622
  function credentialNote(env2, sources) {
@@ -6618,7 +6676,7 @@ async function runOnce(prompt, opts, resumeSessionId) {
6618
6676
  const customBinary = opts.command ?? profile.binary;
6619
6677
  const extraArgs = resumeSessionId === void 0 ? profile.args : [...profile.args ?? [], "--resume", resumeSessionId];
6620
6678
  const env2 = profile.env ? mergeEnv(process.env, profile.env) : void 0;
6621
- if (!existsSync3(join5(root, NOIR_DIR2, ".env"))) {
6679
+ if (!existsSync4(join6(root, NOIR_DIR2, ".env"))) {
6622
6680
  log(
6623
6681
  `This project has no ${NOIR_DIR2}/.env \u2014 that is where Noir reads project credentials and run config from. Run \`noir init\` to scaffold one (\`noir run\` works without it).`,
6624
6682
  opts
@@ -6781,7 +6839,7 @@ function safeTranscript(host, lines) {
6781
6839
  function writeTranscript(host, lines, root = process.cwd()) {
6782
6840
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
6783
6841
  const dir = paths2.transcriptsDir(root);
6784
- const file = join5(dir, `${host}-${ts}.jsonl`);
6842
+ const file = join6(dir, `${host}-${ts}.jsonl`);
6785
6843
  try {
6786
6844
  mkdirSync3(dir, { recursive: true, mode: 448 });
6787
6845
  writeFileSync4(file, `${lines.join("\n")}${lines.length > 0 ? "\n" : ""}`, { mode: 384 });
@@ -6917,8 +6975,8 @@ var init_conflict = __esm({
6917
6975
  });
6918
6976
 
6919
6977
  // src/dedup-write.ts
6920
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync5 } from "fs";
6921
- import { join as join6 } from "path";
6978
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync5 } from "fs";
6979
+ import { join as join7 } from "path";
6922
6980
  import { sha256Hex as sha256Hex2, sha256Hex12 } from "@noir-ai/core";
6923
6981
  async function checkWritePathDedup(root, res, opts) {
6924
6982
  const result = { found: [], conflicts: [] };
@@ -6929,10 +6987,10 @@ async function checkWritePathDedup(root, res, opts) {
6929
6987
  if (embed === void 0) return result;
6930
6988
  const cachedEmbed = makeCachedEmbed(cache, embed);
6931
6989
  for (const proposedRel of writtenHostFiles) {
6932
- const proposedAbs = join6(root, proposedRel);
6990
+ const proposedAbs = join7(root, proposedRel);
6933
6991
  let proposedText;
6934
6992
  try {
6935
- proposedText = readFileSync5(proposedAbs, "utf8");
6993
+ proposedText = readFileSync6(proposedAbs, "utf8");
6936
6994
  } catch {
6937
6995
  continue;
6938
6996
  }
@@ -6976,7 +7034,7 @@ async function checkWritePathDedup(root, res, opts) {
6976
7034
  `
6977
7035
  );
6978
7036
  }
6979
- const matchedText = safeRead(join6(root, matchedRel));
7037
+ const matchedText = safeRead(join7(root, matchedRel));
6980
7038
  result.conflicts.push({
6981
7039
  path: proposedRel,
6982
7040
  mode: "artifact",
@@ -7060,21 +7118,21 @@ async function promptDedupAction(root, proposedRel, matchedRel, proposedText, si
7060
7118
  if (clack.isCancel(choice)) return "preserve";
7061
7119
  if (choice === "skip") {
7062
7120
  try {
7063
- rmSync(join6(root, proposedRel), { force: true });
7121
+ rmSync(join7(root, proposedRel), { force: true });
7064
7122
  } catch {
7065
7123
  }
7066
7124
  return "preserve";
7067
7125
  } else if (choice === "replace") {
7068
7126
  try {
7069
- writeFileSync5(join6(root, matchedRel), proposedText, "utf8");
7127
+ writeFileSync5(join7(root, matchedRel), proposedText, "utf8");
7070
7128
  } catch {
7071
7129
  }
7072
7130
  return "replace";
7073
7131
  } else if (choice === "mirror") {
7074
7132
  try {
7075
- const existing = readFileSync5(join6(root, matchedRel), "utf8");
7133
+ const existing = readFileSync6(join7(root, matchedRel), "utf8");
7076
7134
  writeFileSync5(
7077
- join6(root, matchedRel),
7135
+ join7(root, matchedRel),
7078
7136
  `${existing}
7079
7137
 
7080
7138
  <!-- mirrored from ${proposedRel} -->
@@ -7092,11 +7150,11 @@ function emptyCache() {
7092
7150
  return { version: DEDUP_CACHE_VERSION, model: "", entries: {} };
7093
7151
  }
7094
7152
  function cachePath(root) {
7095
- return join6(root, ".noir", "dedup-cache.json");
7153
+ return join7(root, ".noir", "dedup-cache.json");
7096
7154
  }
7097
7155
  function loadCache(root) {
7098
7156
  try {
7099
- const raw = readFileSync5(cachePath(root), "utf8");
7157
+ const raw = readFileSync6(cachePath(root), "utf8");
7100
7158
  const parsed = JSON.parse(raw);
7101
7159
  if (parsed.version !== DEDUP_CACHE_VERSION) return emptyCache();
7102
7160
  if (typeof parsed.entries !== "object" || parsed.entries === null) return emptyCache();
@@ -7111,7 +7169,7 @@ function loadCache(root) {
7111
7169
  }
7112
7170
  function saveCache(root, cache) {
7113
7171
  try {
7114
- mkdirSync4(join6(root, ".noir"), { recursive: true });
7172
+ mkdirSync4(join7(root, ".noir"), { recursive: true });
7115
7173
  writeFileSync5(cachePath(root), JSON.stringify(cache), "utf8");
7116
7174
  } catch {
7117
7175
  }
@@ -7128,7 +7186,7 @@ function makeCachedEmbed(cache, embed) {
7128
7186
  }
7129
7187
  function safeRead(abs) {
7130
7188
  try {
7131
- return readFileSync5(abs, "utf8");
7189
+ return readFileSync6(abs, "utf8");
7132
7190
  } catch {
7133
7191
  return void 0;
7134
7192
  }
@@ -7166,7 +7224,7 @@ var init_dedup_write = __esm({
7166
7224
  });
7167
7225
 
7168
7226
  // src/init.ts
7169
- import { existsSync as existsSync4 } from "fs";
7227
+ import { existsSync as existsSync5 } from "fs";
7170
7228
  import { resolveAdapter as resolveAdapter3 } from "@noir-ai/adapters";
7171
7229
  import { loadProjectInfo as loadProjectInfo15, paths as paths3 } from "@noir-ai/core";
7172
7230
  import { scaffold } from "@noir-ai/create";
@@ -7231,7 +7289,7 @@ async function init(root, opts) {
7231
7289
  }
7232
7290
  function resolveInitHost(root, opts) {
7233
7291
  if (opts.host !== void 0) return opts.host;
7234
- if (!existsSync4(paths3.config(root))) return "claude";
7292
+ if (!existsSync5(paths3.config(root))) return "claude";
7235
7293
  try {
7236
7294
  return loadProjectInfo15(root).config.host;
7237
7295
  } catch {
@@ -7703,7 +7761,7 @@ var sync_exports = {};
7703
7761
  __export(sync_exports, {
7704
7762
  sync: () => sync
7705
7763
  });
7706
- import { existsSync as existsSync5 } from "fs";
7764
+ import { existsSync as existsSync6 } from "fs";
7707
7765
  import { resolveAdapter as resolveAdapter6 } from "@noir-ai/adapters";
7708
7766
  import { loadProjectInfo as loadProjectInfo19, paths as paths4 } from "@noir-ai/core";
7709
7767
  import { scaffold as scaffold3 } from "@noir-ai/create";
@@ -7776,7 +7834,7 @@ async function sync(root, opts = {}) {
7776
7834
  }
7777
7835
  function resolveSyncHost(root, opts) {
7778
7836
  if (opts.host !== void 0) return opts.host;
7779
- if (!existsSync5(paths4.config(root))) return "claude";
7837
+ if (!existsSync6(paths4.config(root))) return "claude";
7780
7838
  try {
7781
7839
  return loadProjectInfo19(root).config.host;
7782
7840
  } catch {
@@ -8138,6 +8196,9 @@ function createProgram() {
8138
8196
  program2.command("env").description("which configuration is in effect and where each value comes from").action(async (...args) => {
8139
8197
  await env(toCliOptions(actionGlobals(args)));
8140
8198
  });
8199
+ program2.command("hook", { hidden: true }).description("host hook runner (SessionStart / SubagentStart context injection)").action(async () => {
8200
+ await hook();
8201
+ });
8141
8202
  const contextGrp = program2.command("context").description("context engine");
8142
8203
  contextGrp.command("search").description("hybrid search over the indexed context").argument("<query>", "search query").option("--limit <n>", "max results", "10").action(async (...args) => {
8143
8204
  const cmd = trailingCmd(args);
@@ -8525,6 +8586,7 @@ var init_bin = __esm({
8525
8586
  init_env();
8526
8587
  init_handoff();
8527
8588
  init_home();
8589
+ init_hook();
8528
8590
  init_memory();
8529
8591
  init_release();
8530
8592
  init_run();