@noir-ai/core 1.0.0-beta.1 → 1.2.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/index.d.ts CHANGED
@@ -1,7 +1,40 @@
1
1
  import * as z from 'zod';
2
2
 
3
+ type CommentStyle = 'html' | 'hash';
4
+ interface ManagedBlock {
5
+ readonly name: string;
6
+ readonly commentStyle: CommentStyle;
7
+ readonly begin: string;
8
+ readonly end: string;
9
+ }
10
+ /** Build a matched begin/end marker pair for a managed region.
11
+ * `html` → `<!-- noir:<name> begin -->` (markdown / CLAUDE.md / NOIR.md).
12
+ * `hash` → `# >>> noir:<name> >>>` (.gitignore / .dockerignore / .npmignore / yml). */
13
+ declare function managedBlock(name: string, commentStyle?: CommentStyle): ManagedBlock;
14
+ /** Named instances. CONTEXT_BLOCK_* are kept byte-identical for backward compat. */
15
+ declare const CONTEXT_BLOCK: ManagedBlock;
16
+ declare const RULES_BLOCK: ManagedBlock;
17
+ declare const CONTEXT_BLOCK_BEGIN: string;
18
+ declare const CONTEXT_BLOCK_END: string;
19
+
20
+ /** Pick a comment style for a managed region from a file path. */
21
+ declare function commentStyleFor(file: string): CommentStyle;
22
+ /** Remove every `<begin>…<end>` region for `block` from `content`. */
23
+ declare function stripManagedBlock(content: string, block: ManagedBlock): string;
24
+ /** Read the first `<begin>…<end>` region for `block` from `file`, or null if absent/missing. */
25
+ declare function readManagedBlock(file: string, block: ManagedBlock): string | null;
26
+ /** Idempotently write `regionText` (a full `<begin>…<end>` block) into `file`,
27
+ * stripping any prior region for `block` and preserving all other content. */
28
+ declare function writeManagedRegion(file: string, block: ManagedBlock, regionText: string): void;
29
+
3
30
  declare const NoirConfigSchema: z.ZodObject<{
4
- host: z.ZodLiteral<"claude">;
31
+ host: z.ZodDefault<z.ZodEnum<{
32
+ claude: "claude";
33
+ "agents-md": "agents-md";
34
+ gemini: "gemini";
35
+ cursor: "cursor";
36
+ opencode: "opencode";
37
+ }>>;
5
38
  name: z.ZodOptional<z.ZodString>;
6
39
  mode: z.ZodDefault<z.ZodEnum<{
7
40
  full: "full";
@@ -49,10 +82,48 @@ declare const NoirConfigSchema: z.ZodObject<{
49
82
  types: z.ZodOptional<z.ZodArray<z.ZodString>>;
50
83
  }, z.core.$strip>>;
51
84
  }, z.core.$strip>>;
85
+ rules: z.ZodDefault<z.ZodObject<{
86
+ enabled: z.ZodDefault<z.ZodBoolean>;
87
+ lengthBudgetKb: z.ZodDefault<z.ZodNumber>;
88
+ }, z.core.$strip>>;
89
+ prd: z.ZodDefault<z.ZodObject<{
90
+ mandatoryFor: z.ZodDefault<z.ZodArray<z.ZodEnum<{
91
+ feature: "feature";
92
+ epic: "epic";
93
+ enhancement: "enhancement";
94
+ bugfix: "bugfix";
95
+ spike: "spike";
96
+ "quick-task": "quick-task";
97
+ refactor: "refactor";
98
+ }>>>;
99
+ }, z.core.$strip>>;
100
+ integrations: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
101
+ auth: z.ZodDefault<z.ZodObject<{
102
+ tokenEnv: z.ZodOptional<z.ZodOptional<z.ZodString>>;
103
+ }, z.core.$strip>>;
104
+ runtime: z.ZodDefault<z.ZodEnum<{
105
+ none: "none";
106
+ "gated-write-proxy": "gated-write-proxy";
107
+ "mcp-stdio": "mcp-stdio";
108
+ "external-mcp": "external-mcp";
109
+ }>>;
110
+ teamId: z.ZodOptional<z.ZodString>;
111
+ listId: z.ZodOptional<z.ZodString>;
112
+ spaceId: z.ZodOptional<z.ZodString>;
113
+ }, z.core.$strip>>>;
52
114
  }, z.core.$strip>;
53
115
  type NoirConfig = z.infer<typeof NoirConfigSchema>;
54
116
  declare function parseConfig(raw: unknown): NoirConfig;
55
117
 
118
+ /** Managed-region marker for ignore-style files (hash comments). */
119
+ declare const IGNORE_BLOCK: ManagedBlock;
120
+ /** Idempotently write Noir's managed ignore block into each configured ignore
121
+ * file under `root`, preserving all user content outside the block. Returns
122
+ * the list of files touched. Safe to re-run (managed-block replacement). */
123
+ declare function syncIgnores(root: string): {
124
+ files: string[];
125
+ };
126
+
56
127
  declare const NOIR_DIR = ".noir";
57
128
  /**
58
129
  * User-global Noir home: `~/.noir` (HOME-relative, NOT project `.noir/`).
@@ -76,12 +147,15 @@ declare function modelsDir(): string;
76
147
  declare const paths: {
77
148
  readonly noirDir: (root: string) => string;
78
149
  readonly noirMd: (root: string) => string;
150
+ readonly rulesMd: (root: string) => string;
79
151
  readonly config: (root: string) => string;
80
152
  readonly projectId: (root: string) => string;
81
153
  readonly storeDir: (root: string) => string;
82
154
  readonly storeDb: (root: string, projectId: string) => string;
83
155
  readonly specsDir: (root: string) => string;
84
156
  readonly specFile: (root: string, taskId: string, slug: string) => string;
157
+ readonly prdDir: (root: string) => string;
158
+ readonly prdFile: (root: string, taskId: string, slug: string) => string;
85
159
  readonly plansDir: (root: string) => string;
86
160
  readonly planFile: (root: string, taskId: string, slug: string) => string;
87
161
  readonly tasksDir: (root: string) => string;
@@ -92,9 +166,6 @@ declare const paths: {
92
166
  readonly auditFile: (root: string, taskId: string) => string;
93
167
  };
94
168
 
95
- declare const CONTEXT_BLOCK_BEGIN = "<!-- noir:context begin -->";
96
- declare const CONTEXT_BLOCK_END = "<!-- noir:context end -->";
97
-
98
169
  /** Canonical, machine-stable project identity. NEVER a filesystem path. */
99
170
  type ProjectId = string;
100
171
  declare function createProjectId(): ProjectId;
@@ -109,4 +180,4 @@ declare function loadProjectInfo(root: string): ProjectInfo;
109
180
 
110
181
  declare const NOIR_VERSION: string;
111
182
 
112
- export { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, NOIR_DIR, NOIR_VERSION, type NoirConfig, NoirConfigSchema, type ProjectId, type ProjectInfo, createProjectId, loadProjectInfo, modelsDir, noirHome, parseConfig, paths };
183
+ export { CONTEXT_BLOCK, CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, type CommentStyle, IGNORE_BLOCK, type ManagedBlock, NOIR_DIR, NOIR_VERSION, type NoirConfig, NoirConfigSchema, type ProjectId, type ProjectInfo, RULES_BLOCK, commentStyleFor, createProjectId, loadProjectInfo, managedBlock, modelsDir, noirHome, parseConfig, paths, readManagedBlock, stripManagedBlock, syncIgnores, writeManagedRegion };
package/dist/index.js CHANGED
@@ -1,7 +1,61 @@
1
+ // src/block-writer.ts
2
+ import { readFileSync, writeFileSync } from "fs";
3
+ import { basename, extname } from "path";
4
+ var HASH_FILES = /* @__PURE__ */ new Set([
5
+ ".gitignore",
6
+ ".dockerignore",
7
+ ".npmignore",
8
+ ".prettierignore",
9
+ ".eslintignore",
10
+ ".ignore"
11
+ ]);
12
+ function escapeRe(s) {
13
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14
+ }
15
+ function commentStyleFor(file) {
16
+ const lower = file.toLowerCase();
17
+ if (HASH_FILES.has(basename(lower))) return "hash";
18
+ const ext = extname(lower);
19
+ if (ext === ".yml" || ext === ".yaml") return "hash";
20
+ return "html";
21
+ }
22
+ function stripManagedBlock(content, block) {
23
+ const re = new RegExp(`${escapeRe(block.begin)}[\\s\\S]*?${escapeRe(block.end)}\\n?`, "g");
24
+ return content.replace(re, "");
25
+ }
26
+ function readManagedBlock(file, block) {
27
+ let content;
28
+ try {
29
+ content = readFileSync(file, "utf8");
30
+ } catch {
31
+ return null;
32
+ }
33
+ const m = content.match(new RegExp(`${escapeRe(block.begin)}[\\s\\S]*?${escapeRe(block.end)}`));
34
+ return m?.[0] ? m[0] : null;
35
+ }
36
+ function writeManagedRegion(file, block, regionText) {
37
+ let content = "";
38
+ try {
39
+ content = readFileSync(file, "utf8");
40
+ } catch {
41
+ }
42
+ const stripped = stripManagedBlock(content, block);
43
+ const next = `${stripped ? `${stripped.trimEnd()}
44
+
45
+ ` : ""}${regionText}`;
46
+ writeFileSync(file, next, "utf8");
47
+ }
48
+
1
49
  // src/config.ts
2
50
  import * as z from "zod";
3
51
  var NoirConfigSchema = z.object({
4
- host: z.literal("claude"),
52
+ // S10 multi-host: widens from `z.literal('claude')` to a 5-value enum. The
53
+ // canonical `HostId` union lives in @noir-ai/adapters (single owner); core
54
+ // owns the enum STRING here so it does not import adapters (no core→adapters
55
+ // cycle — mirrors the existing pattern where `model`/`memory`/`context`/`prd`
56
+ // redeclare shapes their resolvers consume). `claude` remains the default so
57
+ // existing single-host projects are byte-equivalent (regression anchor).
58
+ host: z.enum(["claude", "agents-md", "gemini", "cursor", "opencode"]).default("claude"),
5
59
  name: z.string().optional(),
6
60
  mode: z.enum(["full", "quick"]).default("full"),
7
61
  daemon: z.object({
@@ -112,63 +166,170 @@ var NoirConfigSchema = z.object({
112
166
  // Restrict candidates to these types (default: every non-`lesson`).
113
167
  types: z.array(z.string()).optional()
114
168
  }).default({ enabled: false })
115
- }).default({ consolidation: { enabled: false } })
169
+ }).default({ consolidation: { enabled: false } }),
170
+ // Debt-batch A — `rules:` block. Additive, no-op when absent (defaults make it
171
+ // a pass-through). Reserved for the upcoming rule-driven lint surface (the
172
+ // `@noir-ai/workflow` rule registry + future `noir check`); for v1.x this
173
+ // block is parsed + validated but no consumer reads it yet — declaring it
174
+ // here lets early adopters pin `enabled: false` or a non-default budget in
175
+ // `.noir/config.yml` without a schema churn when the rule engine ships.
176
+ rules: z.object({
177
+ // Master switch (default true — the rule registry is opt-OUT; a project
178
+ // that wants no part of it sets `enabled: false`). When the rule engine
179
+ // ships, false ⇒ no rules registered + `noir check` is a no-op.
180
+ enabled: z.boolean().default(true),
181
+ // Soft per-rule body budget in KB (targets file-size discipline for the
182
+ // markdown rules; the future rule registry clips over-budget rule bodies
183
+ // and surfaces a warning rather than rejecting the file). Positive int.
184
+ lengthBudgetKb: z.number().int().positive().default(6)
185
+ }).default({ enabled: true, lengthBudgetKb: 6 }),
186
+ // Slice P (PRD) — `prd:` block. Additive, escapable-soft-gate config for the
187
+ // pre-SDD Product Requirements Document. The workflow engine reads
188
+ // `mandatoryFor` to decide when a missing PRD warrants an observable,
189
+ // escapable recommendation at the spec gate (the advance still proceeds —
190
+ // never a hard block; `--force <reason>` is the explicit override). The
191
+ // `TaskClass` enum mirrors @noir-ai/workflow's; duplicated HERE (as a literal
192
+ // z.enum) so core never imports workflow (no core→workflow cycle), exactly
193
+ // as `model`/`memory`/`context` declare provider shapes that their resolvers
194
+ // in other packages consume.
195
+ //
196
+ // Defaults: `mandatoryFor: ['feature', 'epic']` — the noir-prd skill's "when
197
+ // to draft" rule. Quick-mode + non-mandatory taskClasses skip the check
198
+ // entirely; a present-but-empty `prd:` block also degrades to the default.
199
+ prd: z.object({
200
+ mandatoryFor: z.array(
201
+ z.enum(["feature", "epic", "enhancement", "bugfix", "spike", "quick-task", "refactor"])
202
+ ).default(["feature", "epic"])
203
+ }).default({ mandatoryFor: ["feature", "epic"] }),
204
+ // Slice X integration layer (@noir-ai/skills `integrations/<name>/`). Additive
205
+ // block keyed by integration name — every field optional + default-`{}` so a
206
+ // config with NO `integrations:` block still parses and behaves as "no
207
+ // integrations wired" (degraded by default; the skill playbook still ships
208
+ // and works in manual-paste mode regardless of this block — it only carries
209
+ // the per-workspace binding like team_id/list_id for the gated proxy). The
210
+ // declaration shape lives in @noir-ai/skills' Zod schema; this block is the
211
+ // USER-facing overlay (which integration is enabled + where it points), not
212
+ // a re-statement of the declaration.
213
+ //
214
+ // Doctrine (slice-x spec + Q4b): the auth TOKEN stays in env (tokenEnv name
215
+ // is declared in integration.json, NOT here); ClickUp-specific ids are
216
+ // optional workspace binding; `runtime` mirrors the declaration tiers so a
217
+ // user can DOWNGRADE an integration locally (e.g. force `none` for a
218
+ // read-only run).
219
+ integrations: z.record(
220
+ z.string(),
221
+ z.object({
222
+ auth: z.object({
223
+ // Override the token env var name (defaults to the declaration's
224
+ // `auth.tokenEnv` when undefined). Min(1) matches the declaration
225
+ // schema's invariant (`integrations-schema.ts` tokenEnv is non-empty)
226
+ // — an empty-string override must FAIL validation, not silently
227
+ // disable the integration (env[''] is always undefined).
228
+ tokenEnv: z.string().min(1).optional()
229
+ }).partial().default({}),
230
+ runtime: z.enum(["none", "gated-write-proxy", "mcp-stdio", "external-mcp"]).default("none"),
231
+ // ClickUp-workspace binding (optional; flows 3 + 5 need a list_id).
232
+ teamId: z.string().optional(),
233
+ listId: z.string().optional(),
234
+ spaceId: z.string().optional()
235
+ })
236
+ ).default({})
116
237
  });
117
238
  function parseConfig(raw) {
118
239
  return NoirConfigSchema.parse(raw);
119
240
  }
120
241
 
242
+ // src/ignore-manager.ts
243
+ import { join } from "path";
244
+
245
+ // src/markers.ts
246
+ function managedBlock(name, commentStyle = "html") {
247
+ if (commentStyle === "hash") {
248
+ return { name, commentStyle, begin: `# >>> noir:${name} >>>`, end: `# <<< noir:${name} <<<` };
249
+ }
250
+ return {
251
+ name,
252
+ commentStyle,
253
+ begin: `<!-- noir:${name} begin -->`,
254
+ end: `<!-- noir:${name} end -->`
255
+ };
256
+ }
257
+ var CONTEXT_BLOCK = managedBlock("context", "html");
258
+ var RULES_BLOCK = managedBlock("rules", "html");
259
+ var CONTEXT_BLOCK_BEGIN = CONTEXT_BLOCK.begin;
260
+ var CONTEXT_BLOCK_END = CONTEXT_BLOCK.end;
261
+
262
+ // src/ignore-manager.ts
263
+ var IGNORE_BLOCK = managedBlock("ignore", "hash");
264
+ var IGNORE_ENTRIES = [
265
+ [".gitignore", ["/.noir/store/", "/.noir/*.sock", "/.noir/daemon.pid", "/.noir/state/"]],
266
+ [".dockerignore", [".noir/"]],
267
+ [".npmignore", ["/.noir/"]],
268
+ [".prettierignore", ["/.noir/"]]
269
+ ];
270
+ function syncIgnores(root) {
271
+ const files = [];
272
+ for (const [name, entries] of IGNORE_ENTRIES) {
273
+ const region = `${IGNORE_BLOCK.begin}
274
+ ${entries.join("\n")}
275
+ ${IGNORE_BLOCK.end}
276
+ `;
277
+ writeManagedRegion(join(root, name), IGNORE_BLOCK, region);
278
+ files.push(name);
279
+ }
280
+ return { files };
281
+ }
282
+
121
283
  // src/layout.ts
122
284
  import { homedir } from "os";
123
- import { join } from "path";
285
+ import { join as join2 } from "path";
124
286
  var NOIR_DIR = ".noir";
125
287
  function noirHome() {
126
- return join(homedir(), NOIR_DIR);
288
+ return join2(homedir(), NOIR_DIR);
127
289
  }
128
290
  function modelsDir() {
129
- return join(noirHome(), "models");
291
+ return join2(noirHome(), "models");
130
292
  }
131
293
  var paths = {
132
- noirDir: (root) => join(root, NOIR_DIR),
133
- noirMd: (root) => join(root, NOIR_DIR, "NOIR.md"),
134
- config: (root) => join(root, NOIR_DIR, "config.yml"),
135
- projectId: (root) => join(root, NOIR_DIR, "project.id"),
136
- storeDir: (root) => join(root, NOIR_DIR, "store"),
137
- storeDb: (root, projectId) => join(root, NOIR_DIR, "store", `${projectId}.db`),
294
+ noirDir: (root) => join2(root, NOIR_DIR),
295
+ noirMd: (root) => join2(root, NOIR_DIR, "NOIR.md"),
296
+ rulesMd: (root) => join2(root, NOIR_DIR, "rules", "RULES.md"),
297
+ config: (root) => join2(root, NOIR_DIR, "config.yml"),
298
+ projectId: (root) => join2(root, NOIR_DIR, "project.id"),
299
+ storeDir: (root) => join2(root, NOIR_DIR, "store"),
300
+ storeDb: (root, projectId) => join2(root, NOIR_DIR, "store", `${projectId}.db`),
138
301
  // Artifact directories and files
139
- specsDir: (root) => join(root, NOIR_DIR, "specs"),
140
- specFile: (root, taskId, slug) => join(root, NOIR_DIR, "specs", `${taskId}-${slug}.md`),
141
- plansDir: (root) => join(root, NOIR_DIR, "plans"),
142
- planFile: (root, taskId, slug) => join(root, NOIR_DIR, "plans", `${taskId}-${slug}.md`),
143
- tasksDir: (root) => join(root, NOIR_DIR, "tasks"),
144
- taskFile: (root, taskId, taskName) => join(root, NOIR_DIR, "tasks", `${taskId}-${taskName}.md`),
145
- decisionsDir: (root) => join(root, NOIR_DIR, "decisions"),
146
- decisionFile: (root, n) => join(root, NOIR_DIR, "decisions", `${String(n).padStart(4, "0")}.md`),
147
- auditDir: (root) => join(root, NOIR_DIR, "audit"),
148
- auditFile: (root, taskId) => join(root, NOIR_DIR, "audit", `${taskId}.json`)
302
+ specsDir: (root) => join2(root, NOIR_DIR, "specs"),
303
+ specFile: (root, taskId, slug) => join2(root, NOIR_DIR, "specs", `${taskId}-${slug}.md`),
304
+ prdDir: (root) => join2(root, NOIR_DIR, "prd"),
305
+ prdFile: (root, taskId, slug) => join2(root, NOIR_DIR, "prd", `${taskId}-${slug}.md`),
306
+ plansDir: (root) => join2(root, NOIR_DIR, "plans"),
307
+ planFile: (root, taskId, slug) => join2(root, NOIR_DIR, "plans", `${taskId}-${slug}.md`),
308
+ tasksDir: (root) => join2(root, NOIR_DIR, "tasks"),
309
+ taskFile: (root, taskId, taskName) => join2(root, NOIR_DIR, "tasks", `${taskId}-${taskName}.md`),
310
+ decisionsDir: (root) => join2(root, NOIR_DIR, "decisions"),
311
+ decisionFile: (root, n) => join2(root, NOIR_DIR, "decisions", `${String(n).padStart(4, "0")}.md`),
312
+ auditDir: (root) => join2(root, NOIR_DIR, "audit"),
313
+ auditFile: (root, taskId) => join2(root, NOIR_DIR, "audit", `${taskId}.json`)
149
314
  };
150
315
 
151
- // src/markers.ts
152
- var CONTEXT_BLOCK_BEGIN = "<!-- noir:context begin -->";
153
- var CONTEXT_BLOCK_END = "<!-- noir:context end -->";
154
-
155
316
  // src/project.ts
156
- import { readFileSync } from "fs";
157
- import { basename } from "path";
317
+ import { readFileSync as readFileSync2 } from "fs";
318
+ import { basename as basename2 } from "path";
158
319
  import { parse as parseYaml } from "yaml";
159
320
  function loadProjectInfo(root) {
160
321
  let rawId;
161
322
  let rawConfig;
162
323
  try {
163
- rawId = readFileSync(paths.projectId(root), "utf8").trim();
164
- rawConfig = parseYaml(readFileSync(paths.config(root), "utf8"));
324
+ rawId = readFileSync2(paths.projectId(root), "utf8").trim();
325
+ rawConfig = parseYaml(readFileSync2(paths.config(root), "utf8"));
165
326
  } catch {
166
327
  throw new Error(`Noir is not initialized in ${root}. Run \`noir init\` first.`);
167
328
  }
168
329
  const config = parseConfig(rawConfig);
169
330
  return {
170
331
  id: rawId,
171
- name: config.name ?? basename(root),
332
+ name: config.name ?? basename2(root),
172
333
  root,
173
334
  config
174
335
  };
@@ -181,21 +342,30 @@ function createProjectId() {
181
342
  }
182
343
 
183
344
  // src/version.ts
184
- import { readFileSync as readFileSync2 } from "fs";
345
+ import { readFileSync as readFileSync3 } from "fs";
185
346
  import { fileURLToPath } from "url";
186
347
  var pkgPath = fileURLToPath(new URL("../package.json", import.meta.url));
187
- var NOIR_VERSION = JSON.parse(readFileSync2(pkgPath, "utf8")).version ?? "0.0.0";
348
+ var NOIR_VERSION = JSON.parse(readFileSync3(pkgPath, "utf8")).version ?? "0.0.0";
188
349
  export {
350
+ CONTEXT_BLOCK,
189
351
  CONTEXT_BLOCK_BEGIN,
190
352
  CONTEXT_BLOCK_END,
353
+ IGNORE_BLOCK,
191
354
  NOIR_DIR,
192
355
  NOIR_VERSION,
193
356
  NoirConfigSchema,
357
+ RULES_BLOCK,
358
+ commentStyleFor,
194
359
  createProjectId,
195
360
  loadProjectInfo,
361
+ managedBlock,
196
362
  modelsDir,
197
363
  noirHome,
198
364
  parseConfig,
199
- paths
365
+ paths,
366
+ readManagedBlock,
367
+ stripManagedBlock,
368
+ syncIgnores,
369
+ writeManagedRegion
200
370
  };
201
371
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/layout.ts","../src/markers.ts","../src/project.ts","../src/project-id.ts","../src/version.ts"],"sourcesContent":["import * as z from 'zod';\n\nexport const NoirConfigSchema = z.object({\n host: z.literal('claude'),\n name: z.string().optional(),\n mode: z.enum(['full', 'quick']).default('full'),\n daemon: z\n .object({\n idleTimeoutSec: z.number().int().positive().default(900),\n port: z.number().int().min(0).max(65535).optional(),\n })\n .default({ idleTimeoutSec: 900 }),\n // Slice S6 context layer (@noir-ai/context). Mirrors the `daemon` idiom — a\n // top-level object with `.default({})` so a config with NO `context:` block\n // still parses and behaves as local-embedder-attempted (AC-7 / NFR-6). The\n // embedder shape is `kind`-based to match the discriminated `EmbedderConfig`\n // the context factory consumes; `resolveEmbedderConfig` (@noir-ai/context) is\n // the single bridge from this user-facing schema to the factory input, so core\n // never imports context (no core→context cycle). Provider-explicit, NEVER\n // silent remote (blueprint D6): `kind:'remote'`/`'ollama'` are opt-in only;\n // the default `'local'` is in-process, offline, free, private.\n context: z\n .object({\n embedder: z\n .object({\n kind: z.enum(['local', 'remote', 'ollama', 'none']).default('local'),\n // HF repo id (local) / provider model id (remote) / Ollama tag.\n model: z.string().optional(),\n // Remote provider key, e.g. 'openai' (only meaningful when kind:'remote').\n // Free-form string so openai-compatible providers stay expressible; the\n // resolver maps known names (openai/voyage/cohere) to their API-key env var.\n provider: z.string().optional(),\n // Ollama base URL, e.g. http://localhost:11434 (only when kind:'ollama').\n baseURL: z.string().optional(),\n // Target dimensionality — must be 384 to match the existing vec0 table.\n dim: z.number().int().positive().default(384),\n })\n .default({ kind: 'local', dim: 384 }),\n // Configured index roots (informational in core; the daemon/indexer consume).\n roots: z.array(z.string()).default([]),\n // Default token budget for a `context_search` result set (retriever default).\n budgetTokens: z.number().int().positive().default(4096),\n })\n // Zod v4 requires the outer default to match the parsed output shape (every\n // inner field already carries its own default, so an absent `context:` block\n // still resolves to local-embedder/empty-roots/4096). Mirrors `daemon:`.\n .default({ embedder: { kind: 'local', dim: 384 }, roots: [], budgetTokens: 4096 }),\n // Slice S8 bounded model layer (@noir-ai/model). Mirrors the `daemon` idiom —\n // a top-level object with `.default({})` so a config with NO `model:` block\n // still parses and behaves as fully-degraded (every `complete()` call returns\n // `null`; callers substitute a template — the always-available offline path,\n // blueprint D5 / DS-5). `resolveModelConfig` (@noir-ai/model) is the single\n // bridge from this user-facing schema to the runtime shape `complete()`\n // consumes, so core never imports model (no core→model cycle).\n //\n // Provider-EXPLICIT, never silent paid (blueprint D5 / DS-6): the provider is\n // resolved ONLY from explicit `defaultProvider` / a tier's provider key. Env-\n // var presence is NEVER consulted to pick a provider — `ANTHROPIC_API_KEY`\n // being set for another tool does NOT activate Anthropic in Noir. No explicit,\n // configured provider ⇒ `null`. Secrets live in env vars only (DS-8):\n // `apiKeyEnv` stores the env-var NAME (`ANTHROPIC_API_KEY`), never the value,\n // so `.noir/config.yml` stays safe to commit and share; `complete()` reads the\n // value at call time. `tiers` map a tier name → provider block key; each\n // `providers[name]` declares the model id, optional `baseURL` (openai-compat),\n // and optional `apiKeyEnv` (omit for anonymous local providers like Ollama).\n model: z\n .object({\n // Fallback provider name (key into `providers`) when a call's tier resolves\n // no explicit provider. Free-form string so openai-compatible providers stay\n // expressible without an enum churn.\n defaultProvider: z.string().optional(),\n // Per-tier provider-key overrides (draft / title / summarize / consolidate).\n // Each value is a key into `providers{}`; resolution is the consumer's job\n // (tier → tier.provider → defaultProvider → providers[name]).\n tiers: z\n .object({\n draft: z.string().optional(),\n title: z.string().optional(),\n summarize: z.string().optional(),\n consolidate: z.string().optional(),\n })\n .optional(),\n // Configured provider blocks, keyed by name. `model` is required (a provider\n // without a model id is meaningless); `apiKeyEnv` is omitted for anonymous\n // local providers (Ollama / LM Studio) which then send no auth header.\n providers: z\n .record(\n z.string(),\n z.object({\n model: z.string(),\n baseURL: z.string().optional(),\n apiKeyEnv: z.string().optional(),\n }),\n )\n .optional(),\n })\n // Absent `model:` block ⇒ `{}` ⇒ every tier/provider is undefined ⇒ full\n // degradation (offline, free, the default). Inner fields are `.optional()`\n // (no inner `.default()`) so a present-but-empty block also degrades.\n .default({}),\n // Slice S7 cross-session memory (@noir-ai/memory). Mirrors the `daemon` idiom —\n // a top-level object with `.default({})` so a config with NO `memory:` block\n // still parses and behaves as consolidation-disabled (the safe default —\n // capture/store/retrieve are always local + free; consolidation is the ONLY\n // LLM touch and it is opt-in + provider-explicit). `resolveMemoryConfig`\n // (@noir-ai/memory) is the single bridge from this user-facing schema to the\n // runtime `MemoryConfig` the engine consumes, so core never imports memory\n // (no core→memory cycle; mirrors @noir-ai/context + @noir-ai/model).\n //\n // Provider-EXPLICIT, never silent paid (blueprint D6 / DS-6): the provider is\n // resolved ONLY from explicit `consolidation.provider`. Env-var presence is\n // NEVER consulted to pick a provider — `ANTHROPIC_API_KEY` being set for\n // another tool does NOT activate consolidation. No explicit, enabled provider\n // ⇒ `runConsolidation` refuses with `'no-provider'` + writes a miss audit, and\n // NO S8 `complete()` call is made (the Agent-Memory anti-pattern, §9). The\n // outer default matches the parsed output shape (Zod v4 requirement).\n memory: z\n .object({\n consolidation: z\n .object({\n // Master switch (default false). When false, `consolidate` refuses +\n // logs (`no-provider`) regardless of provider/model — the first gate.\n enabled: z.boolean().default(false),\n // Provider key, e.g. 'anthropic' | 'openai' | 'ollama'. Free-form\n // string so openai-compatible providers stay expressible without an\n // enum churn; required (alongside enabled) for consolidation to run.\n provider: z.string().optional(),\n // Provider-specific model id (consumed by S8 complete(); optional\n // here only because a future anonymous local provider may not need it\n // — runConsolidation still refuses when model is absent).\n model: z.string().optional(),\n // Restrict candidates to these types (default: every non-`lesson`).\n types: z.array(z.string()).optional(),\n })\n // Absent consolidation block ⇒ disabled. Outer shape matches output.\n .default({ enabled: false }),\n })\n .default({ consolidation: { enabled: false } }),\n});\n\nexport type NoirConfig = z.infer<typeof NoirConfigSchema>;\n\nexport function parseConfig(raw: unknown): NoirConfig {\n return NoirConfigSchema.parse(raw);\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport const NOIR_DIR = '.noir';\n\n/**\n * User-global Noir home: `~/.noir` (HOME-relative, NOT project `.noir/`).\n *\n * Hosts cross-project, user-scoped state — today the daemon record\n * (`~/.noir/daemon.json`, written by @noir-ai/daemon) and the embedding-model\n * cache ({@link modelsDir}). Kept HOME-relative so a single project checkout\n * stays portable across machines (blueprint: `ProjectId` is canonical, never a\n * filesystem path; the project `.noir/` dir never holds machine-local caches).\n */\nexport function noirHome(): string {\n return join(homedir(), NOIR_DIR);\n}\n\n/**\n * User-global cache for downloaded embedding-model weights: `~/.noir/models/`.\n *\n * Used by @noir-ai/context's local embedder (`@huggingface/transformers` pins\n * `env.cacheDir` here on first load). HOME-relative per spec OQ-7 (resolved) —\n * the ~22 MB MiniLM download is user-scoped, not per-project, so projects stay\n * portable and the weight is fetched at most once per machine.\n */\nexport function modelsDir(): string {\n return join(noirHome(), 'models');\n}\n\nexport const paths = {\n noirDir: (root: string) => join(root, NOIR_DIR),\n noirMd: (root: string) => join(root, NOIR_DIR, 'NOIR.md'),\n config: (root: string) => join(root, NOIR_DIR, 'config.yml'),\n projectId: (root: string) => join(root, NOIR_DIR, 'project.id'),\n storeDir: (root: string) => join(root, NOIR_DIR, 'store'),\n storeDb: (root: string, projectId: string) => join(root, NOIR_DIR, 'store', `${projectId}.db`),\n // Artifact directories and files\n specsDir: (root: string) => join(root, NOIR_DIR, 'specs'),\n specFile: (root: string, taskId: string, slug: string) =>\n join(root, NOIR_DIR, 'specs', `${taskId}-${slug}.md`),\n plansDir: (root: string) => join(root, NOIR_DIR, 'plans'),\n planFile: (root: string, taskId: string, slug: string) =>\n join(root, NOIR_DIR, 'plans', `${taskId}-${slug}.md`),\n tasksDir: (root: string) => join(root, NOIR_DIR, 'tasks'),\n taskFile: (root: string, taskId: string, taskName: string) =>\n join(root, NOIR_DIR, 'tasks', `${taskId}-${taskName}.md`),\n decisionsDir: (root: string) => join(root, NOIR_DIR, 'decisions'),\n decisionFile: (root: string, n: number) =>\n join(root, NOIR_DIR, 'decisions', `${String(n).padStart(4, '0')}.md`),\n auditDir: (root: string) => join(root, NOIR_DIR, 'audit'),\n auditFile: (root: string, taskId: string) => join(root, NOIR_DIR, 'audit', `${taskId}.json`),\n} as const;\n","export const CONTEXT_BLOCK_BEGIN = '<!-- noir:context begin -->';\nexport const CONTEXT_BLOCK_END = '<!-- noir:context end -->';\n","import { readFileSync } from 'node:fs';\nimport { basename } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { type NoirConfig, parseConfig } from './config.js';\nimport { paths } from './layout.js';\nimport type { ProjectId } from './project-id.js';\n\nexport interface ProjectInfo {\n id: ProjectId;\n name: string;\n root: string;\n config: NoirConfig;\n}\n\nexport function loadProjectInfo(root: string): ProjectInfo {\n let rawId: string;\n let rawConfig: unknown;\n try {\n rawId = readFileSync(paths.projectId(root), 'utf8').trim();\n rawConfig = parseYaml(readFileSync(paths.config(root), 'utf8'));\n } catch {\n throw new Error(`Noir is not initialized in ${root}. Run \\`noir init\\` first.`);\n }\n const config = parseConfig(rawConfig);\n return {\n id: rawId,\n name: config.name ?? basename(root),\n root,\n config,\n };\n}\n","import { randomUUID } from 'node:crypto';\n\n/** Canonical, machine-stable project identity. NEVER a filesystem path. */\nexport type ProjectId = string;\n\nexport function createProjectId(): ProjectId {\n return randomUUID();\n}\n","import { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\n\n// Read the version from THIS package's package.json at module load (NOT a hardcoded\n// constant) so a release bump (scripts/bump-version.mjs) is reflected everywhere\n// `noir` self-identifies — `noir --version`, `noir doctor`/`status`, the MCP\n// initialize handshake — with zero drift between package.json and the binary.\n//\n// `import.meta.url` resolves correctly in every layout the package ships in:\n// src (vitest): packages/core/src/version.ts → ../package.json = packages/core/package.json\n// dist (tsup): packages/core/dist/index.js → ../package.json = packages/core/package.json\n// published: node_modules/@noir-ai/core/dist/... → ../package.json = @noir-ai/core/package.json\n// (npm ALWAYS ships package.json alongside dist/, so the read is safe in the tarball.)\nconst pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));\nexport const NOIR_VERSION: string =\n (JSON.parse(readFileSync(pkgPath, 'utf8')) as { version?: string }).version ?? '0.0.0';\n"],"mappings":"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","names":["readFileSync"]}
1
+ {"version":3,"sources":["../src/block-writer.ts","../src/config.ts","../src/ignore-manager.ts","../src/markers.ts","../src/layout.ts","../src/project.ts","../src/project-id.ts","../src/version.ts"],"sourcesContent":["import { readFileSync, writeFileSync } from 'node:fs';\nimport { basename, extname } from 'node:path';\nimport type { CommentStyle, ManagedBlock } from './markers.js';\n\nconst HASH_FILES = new Set([\n '.gitignore',\n '.dockerignore',\n '.npmignore',\n '.prettierignore',\n '.eslintignore',\n '.ignore',\n]);\n\nfunction escapeRe(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/** Pick a comment style for a managed region from a file path. */\nexport function commentStyleFor(file: string): CommentStyle {\n const lower = file.toLowerCase();\n if (HASH_FILES.has(basename(lower))) return 'hash';\n const ext = extname(lower);\n if (ext === '.yml' || ext === '.yaml') return 'hash';\n return 'html';\n}\n\n/** Remove every `<begin>…<end>` region for `block` from `content`. */\nexport function stripManagedBlock(content: string, block: ManagedBlock): string {\n const re = new RegExp(`${escapeRe(block.begin)}[\\\\s\\\\S]*?${escapeRe(block.end)}\\\\n?`, 'g');\n return content.replace(re, '');\n}\n\n/** Read the first `<begin>…<end>` region for `block` from `file`, or null if absent/missing. */\nexport function readManagedBlock(file: string, block: ManagedBlock): string | null {\n let content: string;\n try {\n content = readFileSync(file, 'utf8');\n } catch {\n return null;\n }\n const m = content.match(new RegExp(`${escapeRe(block.begin)}[\\\\s\\\\S]*?${escapeRe(block.end)}`));\n return m?.[0] ? m[0] : null;\n}\n\n/** Idempotently write `regionText` (a full `<begin>…<end>` block) into `file`,\n * stripping any prior region for `block` and preserving all other content. */\nexport function writeManagedRegion(file: string, block: ManagedBlock, regionText: string): void {\n let content = '';\n try {\n content = readFileSync(file, 'utf8');\n } catch {\n /* missing → treat as empty */\n }\n const stripped = stripManagedBlock(content, block);\n const next = `${stripped ? `${stripped.trimEnd()}\\n\\n` : ''}${regionText}`;\n writeFileSync(file, next, 'utf8');\n}\n","import * as z from 'zod';\n\nexport const NoirConfigSchema = z.object({\n // S10 multi-host: widens from `z.literal('claude')` to a 5-value enum. The\n // canonical `HostId` union lives in @noir-ai/adapters (single owner); core\n // owns the enum STRING here so it does not import adapters (no core→adapters\n // cycle — mirrors the existing pattern where `model`/`memory`/`context`/`prd`\n // redeclare shapes their resolvers consume). `claude` remains the default so\n // existing single-host projects are byte-equivalent (regression anchor).\n host: z.enum(['claude', 'agents-md', 'gemini', 'cursor', 'opencode']).default('claude'),\n name: z.string().optional(),\n mode: z.enum(['full', 'quick']).default('full'),\n daemon: z\n .object({\n idleTimeoutSec: z.number().int().positive().default(900),\n port: z.number().int().min(0).max(65535).optional(),\n })\n .default({ idleTimeoutSec: 900 }),\n // Slice S6 context layer (@noir-ai/context). Mirrors the `daemon` idiom — a\n // top-level object with `.default({})` so a config with NO `context:` block\n // still parses and behaves as local-embedder-attempted (AC-7 / NFR-6). The\n // embedder shape is `kind`-based to match the discriminated `EmbedderConfig`\n // the context factory consumes; `resolveEmbedderConfig` (@noir-ai/context) is\n // the single bridge from this user-facing schema to the factory input, so core\n // never imports context (no core→context cycle). Provider-explicit, NEVER\n // silent remote (blueprint D6): `kind:'remote'`/`'ollama'` are opt-in only;\n // the default `'local'` is in-process, offline, free, private.\n context: z\n .object({\n embedder: z\n .object({\n kind: z.enum(['local', 'remote', 'ollama', 'none']).default('local'),\n // HF repo id (local) / provider model id (remote) / Ollama tag.\n model: z.string().optional(),\n // Remote provider key, e.g. 'openai' (only meaningful when kind:'remote').\n // Free-form string so openai-compatible providers stay expressible; the\n // resolver maps known names (openai/voyage/cohere) to their API-key env var.\n provider: z.string().optional(),\n // Ollama base URL, e.g. http://localhost:11434 (only when kind:'ollama').\n baseURL: z.string().optional(),\n // Target dimensionality — must be 384 to match the existing vec0 table.\n dim: z.number().int().positive().default(384),\n })\n .default({ kind: 'local', dim: 384 }),\n // Configured index roots (informational in core; the daemon/indexer consume).\n roots: z.array(z.string()).default([]),\n // Default token budget for a `context_search` result set (retriever default).\n budgetTokens: z.number().int().positive().default(4096),\n })\n // Zod v4 requires the outer default to match the parsed output shape (every\n // inner field already carries its own default, so an absent `context:` block\n // still resolves to local-embedder/empty-roots/4096). Mirrors `daemon:`.\n .default({ embedder: { kind: 'local', dim: 384 }, roots: [], budgetTokens: 4096 }),\n // Slice S8 bounded model layer (@noir-ai/model). Mirrors the `daemon` idiom —\n // a top-level object with `.default({})` so a config with NO `model:` block\n // still parses and behaves as fully-degraded (every `complete()` call returns\n // `null`; callers substitute a template — the always-available offline path,\n // blueprint D5 / DS-5). `resolveModelConfig` (@noir-ai/model) is the single\n // bridge from this user-facing schema to the runtime shape `complete()`\n // consumes, so core never imports model (no core→model cycle).\n //\n // Provider-EXPLICIT, never silent paid (blueprint D5 / DS-6): the provider is\n // resolved ONLY from explicit `defaultProvider` / a tier's provider key. Env-\n // var presence is NEVER consulted to pick a provider — `ANTHROPIC_API_KEY`\n // being set for another tool does NOT activate Anthropic in Noir. No explicit,\n // configured provider ⇒ `null`. Secrets live in env vars only (DS-8):\n // `apiKeyEnv` stores the env-var NAME (`ANTHROPIC_API_KEY`), never the value,\n // so `.noir/config.yml` stays safe to commit and share; `complete()` reads the\n // value at call time. `tiers` map a tier name → provider block key; each\n // `providers[name]` declares the model id, optional `baseURL` (openai-compat),\n // and optional `apiKeyEnv` (omit for anonymous local providers like Ollama).\n model: z\n .object({\n // Fallback provider name (key into `providers`) when a call's tier resolves\n // no explicit provider. Free-form string so openai-compatible providers stay\n // expressible without an enum churn.\n defaultProvider: z.string().optional(),\n // Per-tier provider-key overrides (draft / title / summarize / consolidate).\n // Each value is a key into `providers{}`; resolution is the consumer's job\n // (tier → tier.provider → defaultProvider → providers[name]).\n tiers: z\n .object({\n draft: z.string().optional(),\n title: z.string().optional(),\n summarize: z.string().optional(),\n consolidate: z.string().optional(),\n })\n .optional(),\n // Configured provider blocks, keyed by name. `model` is required (a provider\n // without a model id is meaningless); `apiKeyEnv` is omitted for anonymous\n // local providers (Ollama / LM Studio) which then send no auth header.\n providers: z\n .record(\n z.string(),\n z.object({\n model: z.string(),\n baseURL: z.string().optional(),\n apiKeyEnv: z.string().optional(),\n }),\n )\n .optional(),\n })\n // Absent `model:` block ⇒ `{}` ⇒ every tier/provider is undefined ⇒ full\n // degradation (offline, free, the default). Inner fields are `.optional()`\n // (no inner `.default()`) so a present-but-empty block also degrades.\n .default({}),\n // Slice S7 cross-session memory (@noir-ai/memory). Mirrors the `daemon` idiom —\n // a top-level object with `.default({})` so a config with NO `memory:` block\n // still parses and behaves as consolidation-disabled (the safe default —\n // capture/store/retrieve are always local + free; consolidation is the ONLY\n // LLM touch and it is opt-in + provider-explicit). `resolveMemoryConfig`\n // (@noir-ai/memory) is the single bridge from this user-facing schema to the\n // runtime `MemoryConfig` the engine consumes, so core never imports memory\n // (no core→memory cycle; mirrors @noir-ai/context + @noir-ai/model).\n //\n // Provider-EXPLICIT, never silent paid (blueprint D6 / DS-6): the provider is\n // resolved ONLY from explicit `consolidation.provider`. Env-var presence is\n // NEVER consulted to pick a provider — `ANTHROPIC_API_KEY` being set for\n // another tool does NOT activate consolidation. No explicit, enabled provider\n // ⇒ `runConsolidation` refuses with `'no-provider'` + writes a miss audit, and\n // NO S8 `complete()` call is made (the Agent-Memory anti-pattern, §9). The\n // outer default matches the parsed output shape (Zod v4 requirement).\n memory: z\n .object({\n consolidation: z\n .object({\n // Master switch (default false). When false, `consolidate` refuses +\n // logs (`no-provider`) regardless of provider/model — the first gate.\n enabled: z.boolean().default(false),\n // Provider key, e.g. 'anthropic' | 'openai' | 'ollama'. Free-form\n // string so openai-compatible providers stay expressible without an\n // enum churn; required (alongside enabled) for consolidation to run.\n provider: z.string().optional(),\n // Provider-specific model id (consumed by S8 complete(); optional\n // here only because a future anonymous local provider may not need it\n // — runConsolidation still refuses when model is absent).\n model: z.string().optional(),\n // Restrict candidates to these types (default: every non-`lesson`).\n types: z.array(z.string()).optional(),\n })\n // Absent consolidation block ⇒ disabled. Outer shape matches output.\n .default({ enabled: false }),\n })\n .default({ consolidation: { enabled: false } }),\n // Debt-batch A — `rules:` block. Additive, no-op when absent (defaults make it\n // a pass-through). Reserved for the upcoming rule-driven lint surface (the\n // `@noir-ai/workflow` rule registry + future `noir check`); for v1.x this\n // block is parsed + validated but no consumer reads it yet — declaring it\n // here lets early adopters pin `enabled: false` or a non-default budget in\n // `.noir/config.yml` without a schema churn when the rule engine ships.\n rules: z\n .object({\n // Master switch (default true — the rule registry is opt-OUT; a project\n // that wants no part of it sets `enabled: false`). When the rule engine\n // ships, false ⇒ no rules registered + `noir check` is a no-op.\n enabled: z.boolean().default(true),\n // Soft per-rule body budget in KB (targets file-size discipline for the\n // markdown rules; the future rule registry clips over-budget rule bodies\n // and surfaces a warning rather than rejecting the file). Positive int.\n lengthBudgetKb: z.number().int().positive().default(6),\n })\n // Outer default matches the parsed output shape (Zod v4 requirement): an\n // absent `rules:` block resolves to enabled/6 — the registry-active default.\n .default({ enabled: true, lengthBudgetKb: 6 }),\n // Slice P (PRD) — `prd:` block. Additive, escapable-soft-gate config for the\n // pre-SDD Product Requirements Document. The workflow engine reads\n // `mandatoryFor` to decide when a missing PRD warrants an observable,\n // escapable recommendation at the spec gate (the advance still proceeds —\n // never a hard block; `--force <reason>` is the explicit override). The\n // `TaskClass` enum mirrors @noir-ai/workflow's; duplicated HERE (as a literal\n // z.enum) so core never imports workflow (no core→workflow cycle), exactly\n // as `model`/`memory`/`context` declare provider shapes that their resolvers\n // in other packages consume.\n //\n // Defaults: `mandatoryFor: ['feature', 'epic']` — the noir-prd skill's \"when\n // to draft\" rule. Quick-mode + non-mandatory taskClasses skip the check\n // entirely; a present-but-empty `prd:` block also degrades to the default.\n prd: z\n .object({\n mandatoryFor: z\n .array(\n z.enum(['feature', 'epic', 'enhancement', 'bugfix', 'spike', 'quick-task', 'refactor']),\n )\n .default(['feature', 'epic']),\n })\n .default({ mandatoryFor: ['feature', 'epic'] }),\n // Slice X integration layer (@noir-ai/skills `integrations/<name>/`). Additive\n // block keyed by integration name — every field optional + default-`{}` so a\n // config with NO `integrations:` block still parses and behaves as \"no\n // integrations wired\" (degraded by default; the skill playbook still ships\n // and works in manual-paste mode regardless of this block — it only carries\n // the per-workspace binding like team_id/list_id for the gated proxy). The\n // declaration shape lives in @noir-ai/skills' Zod schema; this block is the\n // USER-facing overlay (which integration is enabled + where it points), not\n // a re-statement of the declaration.\n //\n // Doctrine (slice-x spec + Q4b): the auth TOKEN stays in env (tokenEnv name\n // is declared in integration.json, NOT here); ClickUp-specific ids are\n // optional workspace binding; `runtime` mirrors the declaration tiers so a\n // user can DOWNGRADE an integration locally (e.g. force `none` for a\n // read-only run).\n integrations: z\n .record(\n z.string(),\n z.object({\n auth: z\n .object({\n // Override the token env var name (defaults to the declaration's\n // `auth.tokenEnv` when undefined). Min(1) matches the declaration\n // schema's invariant (`integrations-schema.ts` tokenEnv is non-empty)\n // — an empty-string override must FAIL validation, not silently\n // disable the integration (env[''] is always undefined).\n tokenEnv: z.string().min(1).optional(),\n })\n .partial()\n .default({}),\n runtime: z.enum(['none', 'gated-write-proxy', 'mcp-stdio', 'external-mcp']).default('none'),\n // ClickUp-workspace binding (optional; flows 3 + 5 need a list_id).\n teamId: z.string().optional(),\n listId: z.string().optional(),\n spaceId: z.string().optional(),\n }),\n )\n .default({}),\n});\n\nexport type NoirConfig = z.infer<typeof NoirConfigSchema>;\n\nexport function parseConfig(raw: unknown): NoirConfig {\n return NoirConfigSchema.parse(raw);\n}\n","import { join } from 'node:path';\nimport { writeManagedRegion } from './block-writer.js';\nimport { managedBlock } from './markers.js';\n\n/** Managed-region marker for ignore-style files (hash comments). */\nexport const IGNORE_BLOCK = managedBlock('ignore', 'hash');\n\n/** Default Noir entries per ignore file (derived/runtime artifacts only). */\nconst IGNORE_ENTRIES: ReadonlyArray<[file: string, entries: readonly string[]]> = [\n ['.gitignore', ['/.noir/store/', '/.noir/*.sock', '/.noir/daemon.pid', '/.noir/state/']],\n ['.dockerignore', ['.noir/']],\n ['.npmignore', ['/.noir/']],\n ['.prettierignore', ['/.noir/']],\n];\n\n/** Idempotently write Noir's managed ignore block into each configured ignore\n * file under `root`, preserving all user content outside the block. Returns\n * the list of files touched. Safe to re-run (managed-block replacement). */\nexport function syncIgnores(root: string): { files: string[] } {\n const files: string[] = [];\n for (const [name, entries] of IGNORE_ENTRIES) {\n const region = `${IGNORE_BLOCK.begin}\\n${entries.join('\\n')}\\n${IGNORE_BLOCK.end}\\n`;\n writeManagedRegion(join(root, name), IGNORE_BLOCK, region);\n files.push(name);\n }\n return { files };\n}\n","export type CommentStyle = 'html' | 'hash';\n\nexport interface ManagedBlock {\n readonly name: string;\n readonly commentStyle: CommentStyle;\n readonly begin: string;\n readonly end: string;\n}\n\n/** Build a matched begin/end marker pair for a managed region.\n * `html` → `<!-- noir:<name> begin -->` (markdown / CLAUDE.md / NOIR.md).\n * `hash` → `# >>> noir:<name> >>>` (.gitignore / .dockerignore / .npmignore / yml). */\nexport function managedBlock(name: string, commentStyle: CommentStyle = 'html'): ManagedBlock {\n if (commentStyle === 'hash') {\n return { name, commentStyle, begin: `# >>> noir:${name} >>>`, end: `# <<< noir:${name} <<<` };\n }\n return {\n name,\n commentStyle,\n begin: `<!-- noir:${name} begin -->`,\n end: `<!-- noir:${name} end -->`,\n };\n}\n\n/** Named instances. CONTEXT_BLOCK_* are kept byte-identical for backward compat. */\nexport const CONTEXT_BLOCK = managedBlock('context', 'html');\nexport const RULES_BLOCK = managedBlock('rules', 'html');\nexport const CONTEXT_BLOCK_BEGIN = CONTEXT_BLOCK.begin;\nexport const CONTEXT_BLOCK_END = CONTEXT_BLOCK.end;\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nexport const NOIR_DIR = '.noir';\n\n/**\n * User-global Noir home: `~/.noir` (HOME-relative, NOT project `.noir/`).\n *\n * Hosts cross-project, user-scoped state — today the daemon record\n * (`~/.noir/daemon.json`, written by @noir-ai/daemon) and the embedding-model\n * cache ({@link modelsDir}). Kept HOME-relative so a single project checkout\n * stays portable across machines (blueprint: `ProjectId` is canonical, never a\n * filesystem path; the project `.noir/` dir never holds machine-local caches).\n */\nexport function noirHome(): string {\n return join(homedir(), NOIR_DIR);\n}\n\n/**\n * User-global cache for downloaded embedding-model weights: `~/.noir/models/`.\n *\n * Used by @noir-ai/context's local embedder (`@huggingface/transformers` pins\n * `env.cacheDir` here on first load). HOME-relative per spec OQ-7 (resolved) —\n * the ~22 MB MiniLM download is user-scoped, not per-project, so projects stay\n * portable and the weight is fetched at most once per machine.\n */\nexport function modelsDir(): string {\n return join(noirHome(), 'models');\n}\n\nexport const paths = {\n noirDir: (root: string) => join(root, NOIR_DIR),\n noirMd: (root: string) => join(root, NOIR_DIR, 'NOIR.md'),\n rulesMd: (root: string) => join(root, NOIR_DIR, 'rules', 'RULES.md'),\n config: (root: string) => join(root, NOIR_DIR, 'config.yml'),\n projectId: (root: string) => join(root, NOIR_DIR, 'project.id'),\n storeDir: (root: string) => join(root, NOIR_DIR, 'store'),\n storeDb: (root: string, projectId: string) => join(root, NOIR_DIR, 'store', `${projectId}.db`),\n // Artifact directories and files\n specsDir: (root: string) => join(root, NOIR_DIR, 'specs'),\n specFile: (root: string, taskId: string, slug: string) =>\n join(root, NOIR_DIR, 'specs', `${taskId}-${slug}.md`),\n prdDir: (root: string) => join(root, NOIR_DIR, 'prd'),\n prdFile: (root: string, taskId: string, slug: string) =>\n join(root, NOIR_DIR, 'prd', `${taskId}-${slug}.md`),\n plansDir: (root: string) => join(root, NOIR_DIR, 'plans'),\n planFile: (root: string, taskId: string, slug: string) =>\n join(root, NOIR_DIR, 'plans', `${taskId}-${slug}.md`),\n tasksDir: (root: string) => join(root, NOIR_DIR, 'tasks'),\n taskFile: (root: string, taskId: string, taskName: string) =>\n join(root, NOIR_DIR, 'tasks', `${taskId}-${taskName}.md`),\n decisionsDir: (root: string) => join(root, NOIR_DIR, 'decisions'),\n decisionFile: (root: string, n: number) =>\n join(root, NOIR_DIR, 'decisions', `${String(n).padStart(4, '0')}.md`),\n auditDir: (root: string) => join(root, NOIR_DIR, 'audit'),\n auditFile: (root: string, taskId: string) => join(root, NOIR_DIR, 'audit', `${taskId}.json`),\n} as const;\n","import { readFileSync } from 'node:fs';\nimport { basename } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { type NoirConfig, parseConfig } from './config.js';\nimport { paths } from './layout.js';\nimport type { ProjectId } from './project-id.js';\n\nexport interface ProjectInfo {\n id: ProjectId;\n name: string;\n root: string;\n config: NoirConfig;\n}\n\nexport function loadProjectInfo(root: string): ProjectInfo {\n let rawId: string;\n let rawConfig: unknown;\n try {\n rawId = readFileSync(paths.projectId(root), 'utf8').trim();\n rawConfig = parseYaml(readFileSync(paths.config(root), 'utf8'));\n } catch {\n throw new Error(`Noir is not initialized in ${root}. Run \\`noir init\\` first.`);\n }\n const config = parseConfig(rawConfig);\n return {\n id: rawId,\n name: config.name ?? basename(root),\n root,\n config,\n };\n}\n","import { randomUUID } from 'node:crypto';\n\n/** Canonical, machine-stable project identity. NEVER a filesystem path. */\nexport type ProjectId = string;\n\nexport function createProjectId(): ProjectId {\n return randomUUID();\n}\n","import { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\n\n// Read the version from THIS package's package.json at module load (NOT a hardcoded\n// constant) so a release bump (scripts/bump-version.mjs) is reflected everywhere\n// `noir` self-identifies — `noir --version`, `noir doctor`/`status`, the MCP\n// initialize handshake — with zero drift between package.json and the binary.\n//\n// `import.meta.url` resolves correctly in every layout the package ships in:\n// src (vitest): packages/core/src/version.ts → ../package.json = packages/core/package.json\n// dist (tsup): packages/core/dist/index.js → ../package.json = packages/core/package.json\n// published: node_modules/@noir-ai/core/dist/... → ../package.json = @noir-ai/core/package.json\n// (npm ALWAYS ships package.json alongside dist/, so the read is safe in the tarball.)\nconst pkgPath = fileURLToPath(new URL('../package.json', import.meta.url));\nexport const NOIR_VERSION: string =\n (JSON.parse(readFileSync(pkgPath, 'utf8')) as { version?: string }).version ?? '0.0.0';\n"],"mappings":"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","names":["join","readFileSync","basename","readFileSync","basename","readFileSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noir-ai/core",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.2.0-beta.1",
4
4
  "description": "Noir core — shared types, the config schema (NoirConfigSchema), the .noir/ layout, and markers.",
5
5
  "license": "MIT",
6
6
  "author": "agaaaptr",