@noir-ai/cli 1.15.0-beta.1 → 1.16.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.
@@ -2,10 +2,10 @@ import {
2
2
  buildMigrationPlan,
3
3
  install,
4
4
  installManagedNode
5
- } from "./chunk-KETMM3BA.js";
5
+ } from "./chunk-KVJIPKVE.js";
6
6
  export {
7
7
  buildMigrationPlan,
8
8
  install,
9
9
  installManagedNode
10
10
  };
11
- //# sourceMappingURL=install-KA35ONQP.js.map
11
+ //# sourceMappingURL=install-R5U2WTAQ.js.map
@@ -2,12 +2,13 @@ import {
2
2
  buildConflictOpts,
3
3
  checkWritePathDedup,
4
4
  preservedStaleLine,
5
+ reportEnvHeal,
5
6
  reportPlannedWrites
6
- } from "./chunk-5KWETEMF.js";
7
- import "./chunk-TPNJEN4B.js";
7
+ } from "./chunk-6AUAIEFG.js";
8
+ import "./chunk-CQU2INJH.js";
8
9
  import {
9
10
  resolveInteractive
10
- } from "./chunk-KETMM3BA.js";
11
+ } from "./chunk-KVJIPKVE.js";
11
12
 
12
13
  // src/sync.ts
13
14
  import { existsSync } from "fs";
@@ -40,6 +41,7 @@ async function sync(root, opts = {}) {
40
41
  reportPlannedWrites(res);
41
42
  return res;
42
43
  }
44
+ reportEnvHeal(res);
43
45
  if (res.written.length === 0 && res.identical.length > 0) {
44
46
  process.stderr.write("Nothing to sync (Noir-managed files are up to date).\n");
45
47
  }
@@ -92,4 +94,4 @@ function resolveSyncHost(root, opts) {
92
94
  export {
93
95
  sync
94
96
  };
95
- //# sourceMappingURL=sync-AII5WCRO.js.map
97
+ //# sourceMappingURL=sync-GHEPFPLX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/sync.ts"],"sourcesContent":["// `noir sync` — re-emit the runtime subset of the scaffold manifest.\n//\n// Refactor: delegates to `@noir-ai/create`'s `scaffold({mode:'sync'})`,\n// which emits ONLY the regenerate + managedBlock entries (the always-safe-to-\n// rewrite subset). This ADOPTS THE ENGINE'S\n// SEMANTICS and retires the predecessor's ad-hoc behavior:\n//\n// - The engine re-emits `.mcp.json` (regenerate) + NOIR.md brief\n// (managedBlock) + CLAUDE.md context/rules blocks + ignore-file blocks.\n// The predecessor's sync re-emitted only CLAUDE.md blocks + ignores and\n// did NOT refresh `.mcp.json`/NOIR.md — the engine is a strict superset\n// here, so a transport/url change is now picked up by `noir sync`.\n// - The engine does NOT seed `.noir/rules/RULES.md` on sync (RULES.md is\n// skipIfExists, owned by init/create). The predecessor seeded it when\n// missing; init now owns all seeds. (By design: \"sync re-emits\n// generated/managed content; init owns seeds.\")\n//\n// Multi-host: sync resolves the host from `.noir/config.yml` (persisted by\n// `noir init --host <id>` via the config seed's `host: {{host}}` literal) OR\n// from an explicit `--host <id>` override. The resolved adapter drives the\n// manifest + skills emission. Skills emission stays a core sync feature for\n// hosts with a `skillsDir` (claude → `.claude/skills/`; cursor → `.cursor/rules/`\n// as `.mdc`); gemini/agents-md/opencode have no skill concept and the step is\n// skipped. `scaffold({mode:'sync'})` throws when the project isn't initialized\n// (no .noir/project.id), matching the predecessor's `loadProjectInfo` gate.\n\nimport { existsSync } from 'node:fs';\nimport { type HostId, resolveAdapter } from '@noir-ai/adapters';\nimport { loadProjectInfo, paths } from '@noir-ai/core';\nimport { type ScaffoldResult, scaffold } from '@noir-ai/create';\nimport { type CompileTarget, emitSkillsToDir } from '@noir-ai/skills';\nimport { buildConflictOpts } from './conflict.js';\nimport { checkWritePathDedup } from './dedup-write.js';\nimport { preservedStaleLine, reportEnvHeal, reportPlannedWrites } from './init.js';\nimport { resolveInteractive } from './output.js';\n\nexport interface SyncOptions {\n /** `--host <id>` override. When set, takes precedence over the\n * `.noir/config.yml` `host:` field. Useful for re-emitting under a\n * different host without re-init (advanced — the canonical host stays\n * whatever init wrote). */\n host?: HostId;\n /** Overwrite differing regenerated files without prompting (bypasses\n * the conflict menu). */\n force?: boolean;\n /** Three-way merge managed regions (preserve hand-edits inside\n * `<!-- noir:* -->` markers across a template update). DEFAULT TRUE;\n * `--merge` is now a no-op (kept for backward compatibility). */\n merge?: boolean;\n /** Opt OUT of managed-region merge (restore strip-replace). Exposed on\n * the bin as `--no-merge-regions`. When explicitly `false`, hand-edits\n * inside `<!-- noir:* -->` markers are discarded on a template upgrade. */\n mergeManagedRegions?: boolean;\n /** `--dry-run`/`--preview` — report the planned writes to stderr\n * without touching disk. sync still requires a valid `.noir/project.id`\n * (the engine's \"not initialized\" gate fires regardless). */\n dryRun?: boolean;\n /** Alias for `--dry-run`. Kept on the options bag so direct callers can\n * pass either spelling; the bin collapses both flags before dispatch. */\n preview?: boolean;\n}\n\nexport async function sync(root: string, opts: SyncOptions = {}): Promise<ScaffoldResult> {\n const host = resolveSyncHost(root, opts);\n // The engine reads ScaffoldOptions.interactive (hermetic — never\n // process.env). The CLI derives it once from the bridge + TTY/CI/NO_COLOR gate.\n const interactive = resolveInteractive();\n const conflictOpts = buildConflictOpts({ force: opts.force, interactive });\n\n // Load project info once for both the dedup hook (embedder config)\n // and the existing config-host fallback. Best-effort: a missing/corrupt file\n // is left to scaffold's \"not initialized\" gate below.\n let projectInfo: ReturnType<typeof loadProjectInfo> | undefined;\n try {\n projectInfo = loadProjectInfo(root);\n } catch {\n projectInfo = undefined;\n }\n\n // --dry-run/--preview collapse to a single dryRun boolean.\n const dryRun = opts.dryRun === true || opts.preview === true;\n\n const res = await scaffold({\n root,\n mode: 'sync',\n host,\n interactive,\n ...(dryRun ? { dryRun: true } : {}),\n ...conflictOpts,\n // Merge defaults TRUE inside scaffold; only forward an explicit opt-out.\n ...(opts.mergeManagedRegions === false ? { mergeManagedRegions: false } : {}),\n });\n\n // dry-run reports the planned writes and stops BEFORE skills emission +\n // dedup (both would touch disk / load the embedder). The engine still throws\n // its \"not initialized\" gate when `.noir/project.id` is missing, so a dry-run\n // sync on an uninitialized project is a clean error (not a misleading no-op).\n if (dryRun) {\n reportPlannedWrites(res);\n return res;\n }\n\n // The scaffold re-asserts the 0600 mode on `.noir/.env` on every run; name\n // the fix when it actually happened instead of leaving it to be discovered in\n // a later diagnostic.\n reportEnvHeal(res);\n\n // Surface the no-op so users see sync was a true no-op on disk (the\n // scaffold wrote nothing — every runtime file was content-hash identical).\n // Skills emission below still runs (it has its own dedup).\n if (res.written.length === 0 && res.identical.length > 0) {\n process.stderr.write('Nothing to sync (Noir-managed files are up to date).\\n');\n }\n\n const adapter = resolveAdapter(host);\n const skillsDir = adapter.skillsDir?.({ root });\n if (skillsDir === undefined) {\n // Standardized wording — same phrase across init/sync/create so logs\n // grep uniformly. (sync used to phrase this as \"nothing to sync\".)\n process.stderr.write(`host '${host}' has no skill emitter; skipping skills\\n`);\n } else {\n const target: CompileTarget = host;\n // Forward conflictOpts so the skills-emit conflict flow is\n // live in interactive mode (mirrors init.ts). --json/--no-input stays\n // prompt-free via the `interactive: false` guard.\n type SkillEmitOpts = NonNullable<Parameters<typeof emitSkillsToDir>[1]>;\n const skillOpts: SkillEmitOpts = {\n includeIntegrations: true,\n target,\n conflictPolicy: conflictOpts.conflictPolicy,\n interactive,\n };\n if (conflictOpts.onConflict !== undefined) {\n skillOpts.onConflict = conflictOpts.onConflict as SkillEmitOpts['onConflict'];\n }\n const summary = await emitSkillsToDir(skillsDir, skillOpts);\n const relDir = skillsDir.replace(`${root}/`, '');\n process.stderr.write(\n `Synced ${summary.emitted.length} Noir skills to ${relDir}/ (target: ${target}).\\n`,\n );\n // Surface stale-dir pruning so a user can see when a previous Noir\n // version's builtin was removed (the dir was deleted from .claude/skills/).\n // Pure hygiene; never affects correctness of the freshly-emitted pack.\n const pruned = summary.pruned ?? [];\n if (pruned.length > 0) {\n process.stderr.write(\n `Pruned ${pruned.length} stale noir-* skill dir${pruned.length === 1 ? '' : 's'}: ${pruned.join(', ')}\\n`,\n );\n }\n // The emitted count above is skills that are fully current; anything the\n // emit preserved is stale on disk and has to be named, exactly as `noir\n // init` names it.\n const stale = preservedStaleLine(summary.preserved ?? []);\n if (stale !== undefined) process.stderr.write(`${stale}\\n`);\n }\n\n // Write-path semantic dedup. Non-blocking; degrades to a\n // stderr warn-skip when the embedder is unavailable. Records near-dups on\n // `res.conflicts` so `--json` consumers see them without a prompt.\n const dedup = await checkWritePathDedup(root, res, { interactive, project: projectInfo });\n if (dedup.conflicts.length > 0) res.conflicts.push(...dedup.conflicts);\n\n return res;\n}\n\n/** Resolve the sync host: explicit `--host` override > `.noir/config.yml`\n * `host:` field > `'claude'` (default — preserves the v1.1 regression anchor\n * for projects initialized before the multi-host layer). The config read is\n * best-effort: if\n * `.noir/config.yml` is absent OR fails to parse, scaffold's own\n * \"not initialized\" gate (no `.noir/project.id`) fires below with the locked\n * error string. */\nfunction resolveSyncHost(root: string, opts: SyncOptions): HostId {\n if (opts.host !== undefined) return opts.host;\n if (!existsSync(paths.config(root))) return 'claude';\n try {\n return loadProjectInfo(root).config.host;\n } catch {\n return 'claude';\n }\n}\n"],"mappings":";;;;;;;;;;;;;AA0BA,SAAS,kBAAkB;AAC3B,SAAsB,sBAAsB;AAC5C,SAAS,iBAAiB,aAAa;AACvC,SAA8B,gBAAgB;AAC9C,SAA6B,uBAAuB;AAgCpD,eAAsB,KAAK,MAAc,OAAoB,CAAC,GAA4B;AACxF,QAAM,OAAO,gBAAgB,MAAM,IAAI;AAGvC,QAAM,cAAc,mBAAmB;AACvC,QAAM,eAAe,kBAAkB,EAAE,OAAO,KAAK,OAAO,YAAY,CAAC;AAKzE,MAAI;AACJ,MAAI;AACF,kBAAc,gBAAgB,IAAI;AAAA,EACpC,QAAQ;AACN,kBAAc;AAAA,EAChB;AAGA,QAAM,SAAS,KAAK,WAAW,QAAQ,KAAK,YAAY;AAExD,QAAM,MAAM,MAAM,SAAS;AAAA,IACzB;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA,GAAI,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;AAAA,IACjC,GAAG;AAAA;AAAA,IAEH,GAAI,KAAK,wBAAwB,QAAQ,EAAE,qBAAqB,MAAM,IAAI,CAAC;AAAA,EAC7E,CAAC;AAMD,MAAI,QAAQ;AACV,wBAAoB,GAAG;AACvB,WAAO;AAAA,EACT;AAKA,gBAAc,GAAG;AAKjB,MAAI,IAAI,QAAQ,WAAW,KAAK,IAAI,UAAU,SAAS,GAAG;AACxD,YAAQ,OAAO,MAAM,wDAAwD;AAAA,EAC/E;AAEA,QAAM,UAAU,eAAe,IAAI;AACnC,QAAM,YAAY,QAAQ,YAAY,EAAE,KAAK,CAAC;AAC9C,MAAI,cAAc,QAAW;AAG3B,YAAQ,OAAO,MAAM,SAAS,IAAI;AAAA,CAA2C;AAAA,EAC/E,OAAO;AACL,UAAM,SAAwB;AAK9B,UAAM,YAA2B;AAAA,MAC/B,qBAAqB;AAAA,MACrB;AAAA,MACA,gBAAgB,aAAa;AAAA,MAC7B;AAAA,IACF;AACA,QAAI,aAAa,eAAe,QAAW;AACzC,gBAAU,aAAa,aAAa;AAAA,IACtC;AACA,UAAM,UAAU,MAAM,gBAAgB,WAAW,SAAS;AAC1D,UAAM,SAAS,UAAU,QAAQ,GAAG,IAAI,KAAK,EAAE;AAC/C,YAAQ,OAAO;AAAA,MACb,UAAU,QAAQ,QAAQ,MAAM,mBAAmB,MAAM,cAAc,MAAM;AAAA;AAAA,IAC/E;AAIA,UAAM,SAAS,QAAQ,UAAU,CAAC;AAClC,QAAI,OAAO,SAAS,GAAG;AACrB,cAAQ,OAAO;AAAA,QACb,UAAU,OAAO,MAAM,0BAA0B,OAAO,WAAW,IAAI,KAAK,GAAG,KAAK,OAAO,KAAK,IAAI,CAAC;AAAA;AAAA,MACvG;AAAA,IACF;AAIA,UAAM,QAAQ,mBAAmB,QAAQ,aAAa,CAAC,CAAC;AACxD,QAAI,UAAU,OAAW,SAAQ,OAAO,MAAM,GAAG,KAAK;AAAA,CAAI;AAAA,EAC5D;AAKA,QAAM,QAAQ,MAAM,oBAAoB,MAAM,KAAK,EAAE,aAAa,SAAS,YAAY,CAAC;AACxF,MAAI,MAAM,UAAU,SAAS,EAAG,KAAI,UAAU,KAAK,GAAG,MAAM,SAAS;AAErE,SAAO;AACT;AASA,SAAS,gBAAgB,MAAc,MAA2B;AAChE,MAAI,KAAK,SAAS,OAAW,QAAO,KAAK;AACzC,MAAI,CAAC,WAAW,MAAM,OAAO,IAAI,CAAC,EAAG,QAAO;AAC5C,MAAI;AACF,WAAO,gBAAgB,IAAI,EAAE,OAAO;AAAA,EACtC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -5,8 +5,8 @@ import {
5
5
  runAsyncUpdateCheck,
6
6
  update,
7
7
  updateAdvice
8
- } from "./chunk-TPNJEN4B.js";
9
- import "./chunk-KETMM3BA.js";
8
+ } from "./chunk-CQU2INJH.js";
9
+ import "./chunk-KVJIPKVE.js";
10
10
  export {
11
11
  DEFAULT_UPDATE_CONFIG,
12
12
  buildUpdateNotice,
@@ -15,4 +15,4 @@ export {
15
15
  update,
16
16
  updateAdvice
17
17
  };
18
- //# sourceMappingURL=update-XGKJ4KBR.js.map
18
+ //# sourceMappingURL=update-ZFOLOSZ2.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noir-ai/cli",
3
- "version": "1.15.0-beta.1",
3
+ "version": "1.16.0-beta.1",
4
4
  "description": "Noir CLI — the `noir` command tree (commander + Ink dashboard + @clack home menu), the shell entry point to the Noir AI toolkit.",
5
5
  "license": "MIT",
6
6
  "author": "agaaaptr",
@@ -39,6 +39,10 @@
39
39
  ".": {
40
40
  "types": "./dist/index.d.ts",
41
41
  "import": "./dist/index.js"
42
+ },
43
+ "./hygiene-scan": {
44
+ "types": "./dist/hygiene-scan.d.ts",
45
+ "import": "./dist/hygiene-scan.js"
42
46
  }
43
47
  },
44
48
  "files": [
@@ -55,16 +59,18 @@
55
59
  "ora": "^8.1.0",
56
60
  "picocolors": "^1.1.1",
57
61
  "react": "^19.2.0",
58
- "@noir-ai/adapters": "1.15.0-beta.1",
59
- "@noir-ai/context": "1.15.0-beta.1",
60
- "@noir-ai/daemon": "1.15.0-beta.1",
61
- "@noir-ai/create": "1.15.0-beta.1",
62
- "@noir-ai/memory": "1.15.0-beta.1",
63
- "@noir-ai/model": "1.15.0-beta.1",
64
- "@noir-ai/core": "1.15.0-beta.1",
65
- "@noir-ai/skills": "1.15.0-beta.1",
66
- "@noir-ai/store": "1.15.0-beta.1",
67
- "@noir-ai/workflow": "1.15.0-beta.1"
62
+ "string-width": "^8.2.0",
63
+ "strip-ansi": "^7.2.0",
64
+ "@noir-ai/adapters": "1.16.0-beta.1",
65
+ "@noir-ai/context": "1.16.0-beta.1",
66
+ "@noir-ai/daemon": "1.16.0-beta.1",
67
+ "@noir-ai/create": "1.16.0-beta.1",
68
+ "@noir-ai/skills": "1.16.0-beta.1",
69
+ "@noir-ai/core": "1.16.0-beta.1",
70
+ "@noir-ai/model": "1.16.0-beta.1",
71
+ "@noir-ai/memory": "1.16.0-beta.1",
72
+ "@noir-ai/store": "1.16.0-beta.1",
73
+ "@noir-ai/workflow": "1.16.0-beta.1"
68
74
  },
69
75
  "devDependencies": {
70
76
  "@types/node": "^22.17.0",
@@ -72,7 +78,7 @@
72
78
  "ink-testing-library": "^4.0.0"
73
79
  },
74
80
  "scripts": {
75
- "build": "tsup",
81
+ "build": "tsup && tsup --config tsup.hygiene.config.ts",
76
82
  "typecheck": "tsc --noEmit -p tsconfig.test.json"
77
83
  }
78
84
  }