@noir-ai/skills 1.14.0 → 1.15.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.
@@ -28,7 +28,7 @@ Turn a raw idea, ticket, or ambiguous ask into a shared, written understanding o
28
28
  1. **Restate the goal.** In one sentence, say what the user is trying to achieve and why. If `$ARGUMENTS` was provided, start from it.
29
29
  2. **Surface requirements with a structured prompt.** Ask the clarifying questions you genuinely need — but batch them, don't pepper one at a time. On Claude Code, use the `AskUserQuestion` tool (question + up to 4 options) so the user picks instead of types; on hosts without it (Gemini/Cursor/Copilot), ask in plain text. Cover scope, constraints, users, non-goals, and any acceptance you can see.
30
30
  3. **Offer 2-3 distinct approaches.** Present options with explicit trade-offs. Do not collapse to a single path prematurely; the user chooses.
31
- 4. **Record the decision.** Capture the chosen direction + open questions as a spec stub under `.noir/specs/` if the project is Noir-initialized. This is observable, not rhetorical — the SDD engine records the brainstorm checkpoint.
31
+ 4. **Record the decision.** Capture the chosen direction + open questions as a spec stub under `.noir/specs/` if the project is Noir-initialized. This is discipline, not ceremony — the artifact it produces (a spec, a PRD, or a researched direction) is what the later gates read.
32
32
  5. **Hand off.** Point to `noir-spec` (formalize) → then `noir-planning` (break down).
33
33
 
34
34
  ## Verification
@@ -40,7 +40,7 @@ Turn a raw idea, ticket, or ambiguous ask into a shared, written understanding o
40
40
 
41
41
  ## Notes
42
42
 
43
- - Discipline is observable: the SDD engine's gates record that brainstorming happened. This skill is the playbook.
43
+ - Discipline shows up downstream: the spec or PRD you produce here is what the engine's `spec` gate records. This skill is the playbook.
44
44
  - For a genuinely trivial request (a one-liner with a clear answer), say so and skip the full ritual — brainstorming is for creative or ambiguous work.
45
45
  - If the user has a strong opinion, follow it; your job is to surface the space, not to win an argument.
46
46
 
@@ -12,16 +12,17 @@ compatibility: claude · agents-md · gemini · cursor · opencode
12
12
 
13
13
 
14
14
  ## When to use
15
- - When the user triggers this skill.
16
15
 
17
- Check what's wrong. Run `noir doctor` and read every row — it prints one row per named check with a pass/warn/fail status. Checks are added over time, so `noir doctor`'s own output is the authoritative roster (never work from a list memorized elsewhere); the rows worth reading first for configuration are in the Notes below. Advisory, not mandatory — even a red check doesn't block.
16
+ - Diagnosing why a Noir command isn't working — the daemon won't start, the store looks wrong, a key is missing, or the install is broken.
17
+ - After a config or environment change, to confirm the new value actually won.
18
18
 
19
19
  ## Procedure
20
20
 
21
21
  1. **Run `noir doctor`.** In-process — no daemon needed. Read the full output on stderr.
22
- 2. **Surface actionable issues.** A failed check should tell you what to fix. The install row checks Node version + managed runtime (if installed via native installer).
23
- 3. **Fix one at a time.** Don't batch fixes — each fix deserves its own verification that the underlying issue resolved.
24
- 4. **Re-run `noir doctor` to confirm green.**
22
+ 2. **Read every row.** It prints one row per named check with a pass/warn/fail status. Checks are added over time, so the command's own output is the authoritative roster — never work from a list memorized elsewhere.
23
+ 3. **Treat a red row as blocking.** `noir doctor` exits non-zero when any check fails, so a `fail` is a real gate; a `warn` alone does not fail the run.
24
+ 4. **Fix one at a time.** Don't batch fixes — each fix deserves its own verification that the underlying issue resolved.
25
+ 5. **Re-run `noir doctor` to confirm green.**
25
26
 
26
27
  ## When done → next skill
27
28
 
@@ -29,7 +29,7 @@ Drive a written implementation plan task by task, in order. Each task: implement
29
29
  | **Inline (sequential)** | Ordered tasks with dependencies, small-to-medium scope | Drive tasks one-by-one in this session — implement, test, commit, move to next. You stay in control. |
30
30
  | **Subagent-driven** | Independent tasks, fan-out plans, need per-task review | Hand off to `noir-subagent` — dispatch fresh subagent per task with briefs and review gates. |
31
31
  | **Combination** | Mix of sequential + independent tasks | Start inline for dependency-chain tasks, then fan out independent ones via subagents. |
32
- | **Workflow orchestration** | Large plans, "ultracode" active, multi-agent parallelism | Use the host's Workflow tool to orchestrate many agents concurrently with review between stages. Only offered when the host's effort/capability level supports it. |
32
+ | **Workflow orchestration** | Large plans needing many agents in parallel | Use the host's Workflow tool to orchestrate many agents concurrently with review between stages. Only offered when the host exposes that tool and it is active. |
33
33
 
34
34
  **Default:** if the user doesn't choose, use inline (sequential) — it's the safest, most reviewable path.
35
35
 
@@ -38,7 +38,7 @@ Drive a written implementation plan task by task, in order. Each task: implement
38
38
  - **Subagent-driven:** route to `noir-subagent` (it handles briefs, dispatch, review, integration).
39
39
  - **Combination:** execute dependency-chain tasks inline first, then fan out independent ones.
40
40
  - **Workflow:** compose the workflow script (phases, agents, review gates) from the plan, then dispatch.
41
- 4. **Mark progress.** Update the plan's checkboxes `- [x]` as tasks complete. The engine's execute gate records checkpoints observably.
41
+ 4. **Mark progress.** Update the plan's checkboxes `- [x]` as tasks complete — the plan file is the progress record the next session reads.
42
42
  5. **When the plan is done:** all tasks ticked, all tests green, the implementation matches the spec → hand off to `noir-verifying`.
43
43
 
44
44
  ## Verification
@@ -25,7 +25,7 @@ Break an approved spec into a concrete, step-by-step implementation plan. The pl
25
25
  2. **Decompose into tasks.** Break the work into the smallest units that carry their own test cycle and are worth a reviewer's gate. Each task names the files it touches and the interface it produces.
26
26
  3. **Order the tasks by dependency.** Which task must finish before another starts? List the chain.
27
27
  4. **Define the interfaces.** For each task, state what it consumes (from earlier tasks) and what it produces (for later tasks). Be specific — exact function names, parameter types, file paths.
28
- 5. **Write the plan.** Record under `.noir/plans/PL-<NNNN>-<taskId>-<slug>.md`. Use a numbered task list with checkbox syntax (`- [ ]`) so the engine's verify gate can track completion.
28
+ 5. **Write the plan.** Record under `.noir/plans/PL-<NNNN>-<taskId>-<slug>.md`. Use a numbered task list with checkbox syntax (`- [ ]`) so progress is visible at a glance — the checkboxes are a record for you and the next session, not something the engine reads. The verify gate is driven by the shell checks configured under `workflow.gate.verify.checks` and run by `noir task verify`.
29
29
  6. **Hand off.** Point to `noir-executing-plans` to drive the plan task by task.
30
30
 
31
31
  ## Verification
@@ -14,14 +14,18 @@ A PRD captures what and why before the spec captures how. It is the user-facing
14
14
 
15
15
  ## When to use
16
16
 
17
- ## Procedure
18
- 1. **Follow the guidance** in the notes and verification.
19
-
20
17
  - A feature needs stakeholder-facing rationale before a technical spec.
21
18
  - The user says "write a PRD", "why are we building this", "who is this for."
22
19
  - After `noir-brainstorming` when the idea is clear but needs a formal "why."
23
20
  - **Do NOT use:** for the technical implementation plan — that's `noir-planning`.
24
21
 
22
+ ## Procedure
23
+
24
+ 1. **Confirm the "why" exists.** A PRD with no evidence behind it is fiction. Gather from the `noir-brainstorming` output or the task brief; if there is no data, ticket, or user report, collect that before drafting.
25
+ 2. **Confirm it's wanted.** Explicit opt-in — never auto-draft a PRD; ask first.
26
+ 3. **Fill the sections below.** On Claude Code, use `AskUserQuestion` to close gaps (who is the user, what metric); on other hosts, ask in text. Never invent an answer to close a gap.
27
+ 4. **Write to** `.noir/prd/PRD-<NNNN>-<taskId>-<slug>.md`.
28
+
25
29
  ## Sections (Noir template)
26
30
 
27
31
  1. **Problem.** What problem does this solve? For whom?
@@ -34,13 +38,6 @@ A PRD captures what and why before the spec captures how. It is the user-facing
34
38
  8. **Rabbit holes.** Known pitfalls to avoid.
35
39
  9. **Open Questions.** Unresolved items that need human input.
36
40
 
37
- ## Drafting
38
-
39
- 1. Gather from brainstorming output or the task brief.
40
- 2. On Claude Code, use `AskUserQuestion` to fill gaps (who is the user, what metric). On other hosts, ask.
41
- 3. Write to `.noir/prd/PRD-<NNNN>-<taskId>-<slug>.md`.
42
- 4. **Explicit opt-in.** Never auto-draft a PRD — ask first.
43
-
44
41
  ## When done → next skill
45
42
 
46
43
  → `noir-spec` to formalize the technical side. Or is there something else?
@@ -23,7 +23,7 @@ Save durable insights so the next session doesn't start from zero.
23
23
 
24
24
  1. **Identify what's worth keeping.** A decision with a reason, a pattern that recurred, a bug with its root cause, a preference the user stated.
25
25
  2. **Write a short, searchable entry.** Key concepts + why it matters. Keep it focused — one fact per entry.
26
- 3. **Persist it.** On Noir projects, use `noir memory save <content>` (or the `memory_save` MCP tool).
26
+ 3. **Persist it.** On Noir projects, use `noir memory save --content "<what you learned>"` (or the `memory_save` MCP tool).
27
27
  4. **Confirm.** Say what was saved so the user knows it'll survive.
28
28
 
29
29
  ## In a shared workspace
@@ -14,13 +14,18 @@ The project's AI working-rules at `.noir/rules/RULES.md` — the always-on contr
14
14
 
15
15
  ## When to use
16
16
 
17
- ## Procedure
18
- 1. **Follow the guidance** in the notes and verification.
19
-
20
17
  - The user says "add a rule", "update rules", "should this be a rule?"
21
18
  - A convention is being repeated verbally every session — it's time to codify it.
22
19
  - A rule is growing stale — it's time to prune or archive.
23
20
 
21
+ ## Procedure
22
+
23
+ 1. **Question the rule first.** A rule must be active in *every* session to earn its place. If it is only sometimes relevant, it belongs in a skill (on-demand) or a memory (recall-by-need) instead.
24
+ 2. **Read the current rules.** `.noir/rules/RULES.md` is the source of truth. Extend or sharpen an existing line rather than adding a near-duplicate.
25
+ 3. **Write one imperative line.** State the directive, not the rationale. If it needs a paragraph, the reasoning belongs in a skill or an ADR.
26
+ 4. **Edit the source file.** Change `.noir/rules/RULES.md`; the host files (CLAUDE.md, AGENTS.md, and the rest) carry a managed import block that points at it, so they pick the change up on the next `noir sync`. Never hand-edit the generated block.
27
+ 5. **Check the budget.** Run `noir doctor` and read the `rules budget` row.
28
+
24
29
  ## Keeping rules lean
25
30
 
26
31
  1. **One rule, one line.** If it needs a paragraph, it belongs in a skill, a memory, or an ADR.
@@ -32,7 +32,7 @@ One sentence: what are we building and why.
32
32
  ## Authoring rules
33
33
 
34
34
  1. **Goal first, one sentence.** If the goal needs two sentences, it's two features.
35
- 2. **Acceptance criteria are the contract.** The implementation plan maps each criterion to a task; the verify gate checks each one. Write them so a reader can check them off without asking you.
35
+ 2. **Acceptance criteria are the contract.** The implementation plan maps each criterion to a task, and you check them off during `noir-verifying`. The verify gate itself runs the shell checks configured under `workflow.gate.verify.checks` — it does not read the criteria. Write them so a reader can check them off without asking you.
36
36
  3. **Non-goals prevent scope creep.** "We are not doing X" is as important as "we are doing Y."
37
37
  4. **A short spec with sharp boundaries beats a long essay.** 10 crisp lines > 200 fuzzy ones.
38
38
  5. **Reference sibling specs** when this builds on another — don't copy-paste its content.
@@ -37,8 +37,8 @@ If any item fails, return the report to the subagent with the specific issue. Do
37
37
  ## Files as the contract
38
38
 
39
39
  Subagents communicate through FILES, not conversation. Each task writes:
40
- - `task-N-brief.md` — what it was asked (you write before dispatch).
41
- - `task-N-report.md` — what it did + test results (it writes on return).
40
+ - `.noir/subagents/BR-<NNNN>-<slug>.md` — what it was asked (you write before dispatch).
41
+ - `.noir/subagents/RP-<NNNN>-<slug>.md` — what it did + test results (it writes on return).
42
42
 
43
43
  These become the integration record and the `noir-wrap` handoff input. Structured outputs (JSON, CSV, markdown tables) beat prose for machine consumption.
44
44
 
@@ -12,7 +12,7 @@ references:
12
12
 
13
13
  # noir-verifying
14
14
 
15
- Gather evidence before asserting success. Absorbs `noir-verify` and `noir-review` into one gate: run, read, verify, then claim done.
15
+ Gather evidence before asserting success. Covers the former verify and review steps in one gate: run, read, verify, then claim done.
16
16
 
17
17
  ## When to use
18
18
 
@@ -12,9 +12,9 @@ compatibility: claude · agents-md · gemini · cursor · opencode
12
12
 
13
13
 
14
14
  ## When to use
15
- - When the user triggers this skill.
16
15
 
17
- Isolate feature work from the current workspace via git worktrees. One feature, one directory, no cross-contamination.
16
+ - A feature spans multiple files, branches, or dependencies, and you want it isolated from the current checkout.
17
+ - A parallel task would otherwise collide with work in progress in the current tree.
18
18
 
19
19
  ## Procedure
20
20
 
@@ -10,7 +10,7 @@ compatibility: claude · agents-md · gemini · cursor · opencode
10
10
 
11
11
  # noir-wrap
12
12
 
13
- Close a session in a clean, recoverable state. This skill absorbs `noir-document` (update docs/CHANGELOG/memory) — wrap is the superset that covers the full close.
13
+ Close a session in a clean, recoverable state. This skill covers the former document step (update docs/CHANGELOG/memory) — wrap is the superset that covers the full close.
14
14
 
15
15
  ## When to use
16
16
 
@@ -14,14 +14,18 @@ The pack's authoring manual. Every skill must be: valid (passes `validateSkill`)
14
14
 
15
15
  ## When to use
16
16
 
17
- ## Procedure
18
- 1. **Follow the guidance** in the notes and verification.
19
-
20
17
  - Creating a new `noir-*` skill.
21
18
  - Revising an existing skill's body, description, or metadata.
22
19
  - The user says "write a skill", "update the skill", "add a skill for X."
23
20
  - Verifying the pack — `noir skills lint`.
24
21
 
22
+ ## Procedure
23
+
24
+ 1. **Check the boundary.** Search the pack for a sibling that already covers the trigger. Extend it, or sharpen its `description`, rather than adding a near-duplicate.
25
+ 2. **Write the frontmatter, then the body** against the rules below.
26
+ 3. **Add evals.** `evals/<skill>/evals.json` with offline assertions that exercise the skill's directives.
27
+ 4. **Lint the pack.** `noir skills lint` — zero errors before you call it done.
28
+
25
29
  ## Skill writing rules
26
30
 
27
31
  1. **Frontmatter.** `name` = dir name (`noir-<kebab>`); `description` = WHAT+WHEN, ≤1024 chars, trigger-first, keyword-rich, with boundary ("Do NOT use for..."). Add `metadata.{category,version}`, `license`, `compatibility`. For args-taking skills, add `argument-hint`.
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ interface SkillFrontmatter {
5
5
  name: string;
6
6
  description: string;
7
7
  references?: string[];
8
- /** C3 enhancement — canonical agentskills.io optional fields. `metadata` is a
8
+ /** Canonical agentskills.io optional fields. `metadata` is a
9
9
  * string→string map convention; we type the two keys the registry reads. */
10
10
  metadata?: {
11
11
  category?: string;
@@ -66,7 +66,7 @@ interface IntegrationSkill extends BuiltinSkill {
66
66
  interface ValidationResult {
67
67
  ok: boolean;
68
68
  errors: string[];
69
- /** C3 soft-quality warnings (lint-level): thin body, no example, first-person
69
+ /** Soft-quality warnings (lint-level): thin body, no example, first-person
70
70
  * narration, voodoo constants, time-sensitive pins. Present only when >0. */
71
71
  warnings?: string[];
72
72
  }
@@ -99,12 +99,33 @@ interface CompiledIntegration extends CompiledSkill {
99
99
  }
100
100
  interface EmitSummary {
101
101
  dir: string;
102
+ /** Skill names whose bytes on disk are now the compiled pack's — every file
103
+ * was written (a fresh create, an identical rewrite, or a resolution that
104
+ * wrote the template: `replace`, `rename`, `duplicate`). Builtins +
105
+ * integrations. Disjoint from {@link preserved}: a skill with even one
106
+ * file left as the user had it is reported there instead, so `emitted`
107
+ * never claims a skill that is not fully current. */
102
108
  emitted: string[];
103
109
  references: number;
104
- /** Integration names emitted alongside builtins (subset of `emitted`).
105
- * Additive — callers that ignore it (existing cli) still get the builtins in
106
- * `emitted`. */
110
+ /** The integrations this run processed, in emission order. Most land in
111
+ * `emitted` too, but one whose files were preserved lands in
112
+ * {@link preserved} instead — an integration is classified exactly like a
113
+ * builtin. Additive — callers that ignore it (existing cli) still get the
114
+ * builtins in `emitted`. */
107
115
  integrations?: string[];
116
+ /** Skill names still carrying bytes Noir did NOT write, because at least one
117
+ * of their files differed on disk and the conflict resolved to `preserve`
118
+ * (the non-interactive default: CI, `--json`, piped runs). These skills are
119
+ * present in `dir` but their content is older than the pack, so a caller
120
+ * reporting on the run must name them separately rather than counting them
121
+ * as refreshed. A skill here may have had OTHER files written — a
122
+ * half-refreshed skill is still partly stale, and that is the honest
123
+ * direction to report it.
124
+ *
125
+ * Always populated by `emitSkillsToDir` (empty when nothing was preserved);
126
+ * optional so callers that build the shape themselves stay valid, same as
127
+ * {@link pruned}. */
128
+ preserved?: string[];
108
129
  /** Stale `noir-*` directories removed from `dir` after emit (cleanup).
109
130
  * Names a previous Noir version shipped but the current build no longer
110
131
  * does (builtin renamed/removed). Only the `noir-` managed namespace is
@@ -178,10 +199,9 @@ type SkillConflictResolverReturn = SkillConflictResolution | {
178
199
  type SkillConflictResolver = (ctx: SkillConflictContext) => Promise<SkillConflictResolverReturn> | SkillConflictResolverReturn;
179
200
  /**
180
201
  * The set of host-shaped compile targets the compiler knows how to emit. Was
181
- * `'claude'` only through v1.1; widened in S10 to the multi-host enum (mirrors
202
+ * `'claude'` only through v1.1; widened to the full multi-host enum (mirrors
182
203
  * `@noir-ai/adapters`' `HostId` literally — duplicated here so skills does NOT
183
- * add an adapters dependency; the values are an S10-locked contract). See
184
- * `2026-07-25-s10-multihost-design.md` (A1 + the per-adapter emission table).
204
+ * add an adapters dependency; the values are a locked contract).
185
205
  *
186
206
  * - `claude` | `agents-md` | `gemini` | `opencode` → verbatim SKILL.md + refs
187
207
  * (the canonical format; emitted to the host's skill-equivalent dir).
@@ -211,7 +231,7 @@ declare function discoverBuiltin(builtinDir?: string): BuiltinSkill[];
211
231
  declare function discoverIntegrations(integrationsDir?: string): IntegrationSkill[];
212
232
  /**
213
233
  * Discover the full shipped pack — builtins + integrations — in one call. Used
214
- * by `emitSkillsToDir` so `noir init`/`sync` emit BOTH (slice X goal). The
234
+ * by `emitSkillsToDir` so `noir init`/`sync` emit BOTH. The
215
235
  * `integrationsDir` defaults to the sibling of `builtinDir` so a test fixture
216
236
  * that overrides only `builtinDir` cleanly resolves integrations to a
217
237
  * non-existent path (returns `[]`) instead of accidentally picking up the
@@ -244,7 +264,7 @@ declare function withinLineBudget(body: string, max?: number): boolean;
244
264
  declare function chainedReferences(skill: BuiltinSkill): string[];
245
265
  /**
246
266
  * True when the description carries BOTH a WHEN trigger lead AND a WHAT clause.
247
- * The C3 rule: `description` MUST lead with a WHEN cue (existing compiler rule)
267
+ * The rule: `description` MUST lead with a WHEN cue (existing compiler rule)
248
268
  * AND contain a compact WHAT clause naming what the skill does.
249
269
  *
250
270
  * Two canonical shapes both pass:
@@ -272,7 +292,7 @@ declare function parseFrontmatter(md: string): SkillFrontmatter;
272
292
  declare function bodyOf(md: string): string;
273
293
  declare function validateSkill(skill: BuiltinSkill): ValidationResult;
274
294
  /**
275
- * `lintSkill` — the C3 soft quality gate. Errors = `validateSkill` errors (a
295
+ * `lintSkill` — the soft quality gate. Errors = `validateSkill` errors (a
276
296
  * skill that fails validation is broken); warnings = `quality.ts` style rules
277
297
  * (thin body, no examples, first-person narration, …). A skill can validate
278
298
  * clean yet still carry lint warnings the author should resolve.
@@ -296,7 +316,7 @@ declare function lintSkill(skill: BuiltinSkill): {
296
316
  * and `alwaysApply: false` (the agent decides whether to pull the rule via
297
317
  * the description; never auto-applied). The body is the SKILL.md BODY
298
318
  * (frontmatter stripped). Cursor's rule format has no references concept,
299
- * so references are dropped (documented in the S10 spec risks — the body is
319
+ * so references are dropped (documented as a known limitation — the body is
300
320
  * the surface).
301
321
  *
302
322
  * The `target` defaults to `'claude'` for backward compatibility with every
@@ -322,7 +342,7 @@ declare function emitSkillsToDir(targetDir: string, opts?: {
322
342
  builtinDir?: string;
323
343
  integrationsDir?: string;
324
344
  includeIntegrations?: boolean;
325
- /** S10 CompileTarget — selects the per-skill transform. Defaults to
345
+ /** CompileTarget — selects the per-skill transform. Defaults to
326
346
  * `'claude'` (the v1.1 verbatim SKILL.md shape) so every existing caller
327
347
  * stays byte-identical. `'cursor'` compiles each skill to a `.mdc` rule
328
348
  * file (frontmatter `description`/`globs`/`alwaysApply:false` + the
@@ -398,7 +418,7 @@ declare function evaluateSuite(suite: EvalSuite): Array<{
398
418
  failures: string[];
399
419
  }>;
400
420
 
401
- /** Auth shape. Locked: `env-var` only until keychain lands (Q4b — refuse OAuth,
421
+ /** Auth shape. Locked: `env-var` only until keychain lands (refuse OAuth,
402
422
  * never silently lower the security bar). `fallback:'manual-paste'` keeps the
403
423
  * no-token path honest (the playbook tells the user to paste a value); `'none'`
404
424
  * is for integrations that genuinely do not need a token (read-only public
@@ -412,9 +432,9 @@ declare const IntegrationAuthSchema: z.ZodObject<{
412
432
  }>>;
413
433
  }, z.core.$strip>;
414
434
  /** SDD two-way binding. `intakeFrom` declares the external artifact kind the
415
- * `noir-brainstorming` skill pulls from (absorbed noir-intake in C3);
435
+ * `noir-brainstorming` skill pulls from (which absorbed noir-intake);
416
436
  * `writeBack` enumerates the fields `noir-wrap` pushes back at session end
417
- * (absorbed noir-document in C3). Strings (not enums) for `writeBack` so a
437
+ * (which absorbed noir-document). Strings (not enums) for `writeBack` so a
418
438
  * per-integration vocabulary stays expressible without
419
439
  * churning the schema. */
420
440
  declare const IntegrationSddSchema: z.ZodDefault<z.ZodObject<{
@@ -512,7 +532,7 @@ interface SkillRegistryEntry {
512
532
  /** `full` when the body has no `> **Stub:**` marker; `stub` otherwise. */
513
533
  status: 'full' | 'stub';
514
534
  /** Lifecycle stage of the skill. `active` = currently shipped and usable;
515
- * `deprecated` = superseded (a renamed/merged predecessor). C3 spec §8.
535
+ * `deprecated` = superseded (a renamed/merged predecessor).
516
536
  * The curated pack has no deprecated members today. */
517
537
  lifecycle: 'active' | 'deprecated';
518
538
  /** The WHAT+WHEN description (the trigger the host sees). */
@@ -534,4 +554,4 @@ declare function registryByCategory(category: string): SkillRegistryEntry[];
534
554
 
535
555
  declare const FORBIDDEN_RESIDUE: readonly string[];
536
556
 
537
- export { BUILTIN_DIR, type BuiltinReference, type BuiltinSkill, type CompileTarget, type CompiledIntegration, type CompiledSkill, EVALS_DIR, type EmitSummary, type EmittedFile, type EvalAssertion, type EvalSuite, FORBIDDEN_RESIDUE, INTEGRATIONS_DIR, IntegrationAuthSchema, type IntegrationDeclaration$1 as IntegrationDeclaration, IntegrationDeclarationSchema, IntegrationMcpSchema, IntegrationSddSchema, type IntegrationSkill, MAX_BODY_LINES, MIN_FULL_BODY_LINES, NOIR_NAMESPACE, type SkillEval, type SkillFrontmatter, type SkillRegistryEntry, type ValidationResult, bodyOf, buildRegistry, chainedReferences, compileIntegration, compileSkill, discoverAll, discoverBuiltin, discoverIntegrations, emitSkillsToDir, evaluateSuite, isWhatWhenDescription, lintSkill, lintWarnings, loadEvalSuites, looksLikeWhenDescription, missingSections, parseEvalSuite, parseFrontmatter, parseIntegration, registryByCategory, runAssertions, runtimeEmitsHostMcp, validateIntegration, validateSkill, withinLineBudget };
557
+ export { BUILTIN_DIR, type BuiltinReference, type BuiltinSkill, type CompileTarget, type CompiledIntegration, type CompiledSkill, EVALS_DIR, type EmitSummary, type EmittedFile, type EvalAssertion, type EvalSuite, FORBIDDEN_RESIDUE, INTEGRATIONS_DIR, IntegrationAuthSchema, type IntegrationDeclaration$1 as IntegrationDeclaration, IntegrationDeclarationSchema, IntegrationMcpSchema, IntegrationSddSchema, type IntegrationSkill, MAX_BODY_LINES, MIN_FULL_BODY_LINES, NOIR_NAMESPACE, type SkillConflict, type SkillEval, type SkillFrontmatter, type SkillRegistryEntry, type ValidationResult, bodyOf, buildRegistry, chainedReferences, compileIntegration, compileSkill, discoverAll, discoverBuiltin, discoverIntegrations, emitSkillsToDir, evaluateSuite, isWhatWhenDescription, lintSkill, lintWarnings, loadEvalSuites, looksLikeWhenDescription, missingSections, parseEvalSuite, parseFrontmatter, parseIntegration, registryByCategory, runAssertions, runtimeEmitsHostMcp, validateIntegration, validateSkill, withinLineBudget };
package/dist/index.js CHANGED
@@ -186,13 +186,17 @@ function artifactPathDrift(skill) {
186
186
  const codes = DIR_TO_CODES.get(dir);
187
187
  if (codes === void 0) {
188
188
  if (!NON_ARTIFACT_DIRS.has(dir)) {
189
- drifts.add(`.noir/${dir}/ is not a canonical artifact directory (C3 artifact standard)`);
189
+ drifts.add(
190
+ `.noir/${dir}/ is not a canonical artifact directory (see docs/reference/artifact-format.md)`
191
+ );
190
192
  }
191
193
  continue;
192
194
  }
193
195
  const expected = [...codes].join("/");
194
196
  if (![...codes].some((c) => name.startsWith(`${c}-`))) {
195
- drifts.add(`.noir/${dir}/${name} must be ${expected}-<NNNN>-\u2026 (C3 artifact standard)`);
197
+ drifts.add(
198
+ `.noir/${dir}/${name} must be ${expected}-<NNNN>-\u2026 (see docs/reference/artifact-format.md)`
199
+ );
196
200
  }
197
201
  }
198
202
  }
@@ -322,6 +326,8 @@ async function emitSkillsToDir(targetDir, opts = {}) {
322
326
  await mkdir(targetDir, { recursive: true });
323
327
  let references = 0;
324
328
  const emitted = [];
329
+ const preserved = [];
330
+ const kept = [];
325
331
  const integrationNames = [];
326
332
  const conflicts = [];
327
333
  const memory = /* @__PURE__ */ new Map();
@@ -368,43 +374,52 @@ async function emitSkillsToDir(targetDir, opts = {}) {
368
374
  throw new Error(`skill emit cancelled by user at ${relDest}`);
369
375
  }
370
376
  if (resolution === "preserve") {
371
- return;
377
+ return "preserved";
372
378
  }
373
379
  if (resolution === "rename") {
374
380
  await renameAside(absDest, ".local");
375
381
  } else if (resolution === "duplicate") {
376
382
  await writeFile(await uniqueAside(absDest, ".noir"), content, "utf8");
377
- return;
383
+ return "written";
378
384
  }
379
385
  }
380
386
  await writeFile(absDest, content, "utf8");
381
387
  if (isReference) references++;
388
+ return "written";
382
389
  };
383
390
  const flat = target === "cursor";
384
391
  for (const s of builtins) {
385
392
  const compiled = compileSkill(s, target);
393
+ let sawPreserved = false;
386
394
  for (const f of compiled.files) {
387
395
  const dest = flat ? join2(targetDir, ...f.path) : join2(targetDir, s.name, ...f.path);
388
396
  await mkdir(dirname2(dest), { recursive: true });
389
397
  const rel = flat ? f.path.join("/") : [s.name, ...f.path].join("/");
390
- await writeWithConflict(dest, rel, f.content, f.path[0] !== "SKILL.md");
398
+ const outcome = await writeWithConflict(dest, rel, f.content, f.path[0] !== "SKILL.md");
399
+ if (outcome === "preserved") sawPreserved = true;
391
400
  }
392
- emitted.push(s.name);
401
+ kept.push(s.name);
402
+ if (sawPreserved) preserved.push(s.name);
403
+ else emitted.push(s.name);
393
404
  }
394
405
  if (emitIntegrations) {
395
406
  for (const i of integrations) {
396
407
  const compiled = compileIntegration(i, target);
408
+ let sawPreserved = false;
397
409
  for (const f of compiled.files) {
398
410
  const dest = flat ? join2(targetDir, ...f.path) : join2(targetDir, i.name, ...f.path);
399
411
  await mkdir(dirname2(dest), { recursive: true });
400
412
  const rel = flat ? f.path.join("/") : [i.name, ...f.path].join("/");
401
- await writeWithConflict(dest, rel, f.content, f.path[0] !== "SKILL.md");
413
+ const outcome = await writeWithConflict(dest, rel, f.content, f.path[0] !== "SKILL.md");
414
+ if (outcome === "preserved") sawPreserved = true;
402
415
  }
403
- emitted.push(i.name);
416
+ kept.push(i.name);
417
+ if (sawPreserved) preserved.push(i.name);
418
+ else emitted.push(i.name);
404
419
  integrationNames.push(i.name);
405
420
  }
406
421
  }
407
- const keep = new Set(emitted);
422
+ const keep = new Set(kept);
408
423
  const pruned = [];
409
424
  const preservedUserOwned = [];
410
425
  let dirEntries = [];
@@ -451,6 +466,7 @@ async function emitSkillsToDir(targetDir, opts = {}) {
451
466
  emitted,
452
467
  references,
453
468
  integrations: integrationNames,
469
+ preserved,
454
470
  pruned,
455
471
  ...preservedUserOwned.length > 0 ? { preservedUserOwned } : {},
456
472
  ...conflicts.length > 0 ? { conflicts } : {}
@@ -651,7 +667,7 @@ function toEntry(s, kind) {
651
667
  version,
652
668
  status,
653
669
  // Every shipped skill is active; `deprecated` is reserved for a future
654
- // superseded member (C3 spec §8).
670
+ // superseded member.
655
671
  lifecycle: "active",
656
672
  description: typeof s.frontmatter.description === "string" ? s.frontmatter.description : "",
657
673
  referenceCount: s.references.length,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/compiler.ts","../src/discover.ts","../src/integrations-schema.ts","../src/quality.ts","../src/evals.ts","../src/registry.ts","../src/residue.ts"],"sourcesContent":["import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { sha256Hex12, uniqueAsideSync } from '@noir-ai/core';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { discoverAll, discoverBuiltin } from './discover.js';\nimport { runtimeEmitsHostMcp } from './integrations-schema.js';\nimport {\n artifactPathDrift,\n chainedReferences,\n isWhatWhenDescription,\n lintWarnings,\n looksLikeWhenDescription,\n MAX_BODY_LINES,\n missingSections,\n withinLineBudget,\n} from './quality.js';\n\n// Re-exported for backward-compat — callers (hygiene tests, CLI) import\n// `looksLikeWhenDescription` from './compiler.js'. The single source of truth\n// is quality.ts; this alias keeps the old import surface stable.\nexport { looksLikeWhenDescription } from './quality.js';\n\nimport type {\n BuiltinSkill,\n CompiledIntegration,\n CompiledSkill,\n CompileTarget,\n EmitSummary,\n IntegrationSkill,\n SkillConflict,\n SkillConflictContext,\n SkillConflictResolution,\n SkillConflictResolver,\n SkillFrontmatter,\n ValidationResult,\n} from './types.js';\n\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/;\nconst NAME_RE = /^noir-[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst MAX_DESC = 1024;\n\nexport function parseFrontmatter(md: string): SkillFrontmatter {\n const m = md.match(FRONTMATTER_RE);\n if (!m) throw new Error('Skill missing YAML frontmatter (expected --- ... ---)');\n // Group 1 always exists when the regex matches; guard past noUncheckedIndexedAccess.\n const yaml = m[1];\n if (yaml === undefined) throw new Error('Skill missing YAML frontmatter (expected --- ... ---)');\n const fm = parseYaml(yaml) as SkillFrontmatter;\n if (typeof fm?.name !== 'string' || typeof fm?.description !== 'string') {\n throw new Error('Skill frontmatter requires string `name` + `description`');\n }\n return fm;\n}\n\nexport function bodyOf(md: string): string {\n return md.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?/, '');\n}\n\nexport function validateSkill(skill: BuiltinSkill): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n const { name, description, metadata } = skill.frontmatter;\n if (!name) errors.push('missing `name`');\n else if (!NAME_RE.test(name)) errors.push(`name \"${name}\" must match noir-<kebab>`);\n if (basename(skill.dir) !== name) {\n errors.push(`dir \"${basename(skill.dir)}\" must equal name \"${name}\"`);\n }\n if (!description?.trim()) errors.push('missing `description`');\n else if (description.length > MAX_DESC) errors.push(`description exceeds ${MAX_DESC} chars`);\n else if (!looksLikeWhenDescription(description)) {\n errors.push('description must state WHEN to trigger (e.g. \"Use when…\"), not WHAT it does');\n } else if (!isWhatWhenDescription(description)) {\n // C3: the description must ALSO carry a WHAT clause — the trigger phrase\n // alone (WHEN-only) fails. Errors, not warnings: it's part of the contract.\n errors.push('description must be WHAT+WHEN — a WHAT clause after the trigger phrase');\n }\n // C3 structural gate: metadata, required sections, line budget, one-level refs.\n if (!metadata?.category?.trim()) errors.push('missing `metadata.category`');\n if (!metadata?.version?.trim()) errors.push('missing `metadata.version`');\n const body = bodyOf(skill.skillMd);\n const missing = missingSections(body);\n for (const sec of missing) errors.push(`missing required section: ${sec}`);\n if (!withinLineBudget(body)) {\n errors.push(`body exceeds ${MAX_BODY_LINES}-line budget (${body.split('\\n').length} lines)`);\n }\n const chained = chainedReferences(skill);\n for (const r of chained)\n errors.push(`reference \"${r}\" chains to another reference (must be one level deep)`);\n for (const r of skill.references) {\n if (!/^[a-z0-9-]+\\.md$/i.test(r.name)) errors.push(`reference \"${r.name}\" must be <kebab>.md`);\n if (!r.content.trim()) errors.push(`reference \"${r.name}\" is empty`);\n }\n // C3 generated-artifact standard: no `.noir/` output-path drift in the body\n // or references (unknown dir, or a filename missing its type-code prefix).\n for (const d of artifactPathDrift(skill)) errors.push(d);\n // Soft warnings (lint-level) — advisory, non-failing.\n warnings.push(...lintWarnings(skill));\n return { ok: errors.length === 0, errors, warnings: warnings.length > 0 ? warnings : undefined };\n}\n\n/**\n * `lintSkill` — the C3 soft quality gate. Errors = `validateSkill` errors (a\n * skill that fails validation is broken); warnings = `quality.ts` style rules\n * (thin body, no examples, first-person narration, …). A skill can validate\n * clean yet still carry lint warnings the author should resolve.\n */\nexport function lintSkill(skill: BuiltinSkill): {\n name: string;\n errors: string[];\n warnings: string[];\n} {\n const res = validateSkill(skill);\n return { name: skill.name, errors: res.errors, warnings: res.warnings ?? [] };\n}\n\n/**\n * Compile a builtin skill into the host-shaped `CompiledSkill`. The validator\n * runs for every target (a malformed skill is malformed in any format).\n *\n * - `claude` | `agents-md` | `gemini` | `opencode` (the AGENTS.md-aligned\n * hosts): canonical format copied verbatim — `SKILL.md` plus its\n * `references/` siblings. These hosts read the same SKILL.md shape;\n * the EMIT-LOCATION (which dir they land in) is the cli/adapter's job, not\n * the compiler's.\n * - `cursor`: transform into a Cursor `.mdc` rule — ONE file `<name>.mdc`\n * whose frontmatter carries the skill description, a wildcard globs entry,\n * and `alwaysApply: false` (the agent decides whether to pull the rule via\n * the description; never auto-applied). The body is the SKILL.md BODY\n * (frontmatter stripped). Cursor's rule format has no references concept,\n * so references are dropped (documented in the S10 spec risks — the body is\n * the surface).\n *\n * The `target` defaults to `'claude'` for backward compatibility with every\n * existing caller; the verbatim branch produces byte-identical output to v1.1\n * (the regression anchor).\n */\nexport function compileSkill(skill: BuiltinSkill, target: CompileTarget = 'claude'): CompiledSkill {\n const res = validateSkill(skill);\n if (!res.ok) throw new Error(`Cannot compile ${skill.name}: ${res.errors.join('; ')}`);\n if (target === 'cursor') {\n return { name: skill.name, files: [{ path: [`${skill.name}.mdc`], content: toMdc(skill) }] };\n }\n // claude / agents-md / gemini / opencode → canonical verbatim format.\n const files = [\n { path: ['SKILL.md'], content: skill.skillMd },\n ...skill.references.map((r) => ({ path: ['references', r.name], content: r.content })),\n ];\n return { name: skill.name, files };\n}\n\n/**\n * Cursor `.mdc` rule transform — frontmatter `{description, globs, alwaysApply:\n * false}` + the SKILL.md body. The description drives Cursor's agent-decided\n * rule selection (per the S10 spec open-decision default: `alwaysApply: false`).\n * Cursor's rule format has no references/ concept, so the body alone is the\n * surface; references are dropped (documented risk in the spec).\n *\n * Frontmatter is YAML-serialized via the `yaml` package (already a dep) so\n * description escaping (colons, quotes, multi-line) is correct without hand\n * rolling. `lineWidth: 0` keeps descriptions on a single quoted line (predictable\n * byte shape; Cursor tolerates either). */\nfunction toMdc(skill: BuiltinSkill): string {\n const frontmatter = stringifyYaml(\n {\n description: skill.frontmatter.description,\n globs: ['**/*'],\n alwaysApply: false,\n },\n { lineWidth: 0 },\n ).trimEnd();\n const body = bodyOf(skill.skillMd);\n // Cursor's `.mdc` shape: leading `---\\n`, frontmatter, closing `---\\n`, blank\n // line, body. Trailing newline so files concatenate cleanly on disk.\n return `---\\n${frontmatter}\\n---\\n${body.endsWith('\\n') ? body : `${body}\\n`}`;\n}\n\n/**\n * Compile an integration: validates the SKILL.md (same shape as builtins) +\n * renders the same per-skill files. When the integration's `runtime` widens\n * emission (`mcp-stdio`/`external-mcp`) AND it carries a non-null `mcp`\n * declaration, also expose a provider-neutral `hostMcp` server block the host\n * adapter may merge into the host MCP config (e.g. Claude's `.mcp.json`).\n *\n * For `gated-write-proxy` (ClickUp) + `none` → no `hostMcp` (writes route\n * through Noir's own MCP tool; the host MCP config is unchanged). For\n * `mcp-stdio` the hostMcp is exposed (the compiler widens emission); the\n * Claude adapter still does not emit a NEW server entry for it (Noir's own\n * `noir mcp serve` entry already covers it).\n */\nexport function compileIntegration(\n integration: IntegrationSkill,\n target: CompileTarget = 'claude',\n): CompiledIntegration {\n // Validate the skill half first (throws on a bad SKILL.md — same path as\n // builtins). compileSkill returns a base CompiledSkill; we layer hostMcp on.\n const base = compileSkill(integration, target);\n const { runtime, mcp } = integration.declaration;\n if (runtimeEmitsHostMcp(runtime) && mcp !== null) {\n return {\n ...base,\n hostMcp: {\n serverName: integration.name,\n command: mcp.command,\n args: mcp.args,\n transport: mcp.transport,\n url: mcp.url,\n env: mcp.env,\n },\n };\n }\n return base;\n}\n\nexport async function emitSkillsToDir(\n targetDir: string,\n opts: {\n builtinDir?: string;\n integrationsDir?: string;\n includeIntegrations?: boolean;\n /** S10 CompileTarget — selects the per-skill transform. Defaults to\n * `'claude'` (the v1.1 verbatim SKILL.md shape) so every existing caller\n * stays byte-identical. `'cursor'` compiles each skill to a `.mdc` rule\n * file (frontmatter `description`/`globs`/`alwaysApply:false` + the\n * SKILL.md body); the other targets map to the verbatim shape (their\n * hosts have no skill concept, so emit is skipped upstream — but the\n * default-verbatim policy keeps the signature total). */\n target?: CompileTarget;\n /** Policy for a skill file that exists AND differs from the compiled\n * bytes, when no {@link onConflict} resolver is wired (or non-interactive).\n * Default `'overwrite'` preserves the v1.2 behavior (every sync clobbers).\n * `'preserve'` skips the differing file (the CI / non-TTY default the CLI\n * threads via `buildConflictOpts`). */\n conflictPolicy?: 'overwrite' | 'preserve';\n /** Per-file conflict resolver. The CLI passes its single\n * `buildConflictOpts().onConflict` (clack menu + diff preview + apply-to-\n * all) through unchanged; structurally compatible with @noir-ai/create's\n * `ConflictResolverReturn`. */\n onConflict?: SkillConflictResolver;\n /** Explicit interactivity flag (the engine reads THIS, not process.env,\n * matching @noir-ai/create's hermetic-interactive contract). When `false`,\n * the resolver is NEVER consulted (CI / --json never hangs on a prompt). */\n interactive?: boolean;\n } = {},\n): Promise<EmitSummary> {\n const target: CompileTarget = opts.target ?? 'claude';\n const { builtins, integrations } = discoverAll({\n builtinDir: opts.builtinDir,\n integrationsDir: opts.integrationsDir,\n });\n const emitIntegrations = opts.includeIntegrations ?? true;\n // Validate the whole pack before writing anything (fail-fast, atomic-ish).\n for (const s of builtins) {\n const res = validateSkill(s);\n if (!res.ok) throw new Error(`Invalid builtin skill ${s.name}: ${res.errors.join('; ')}`);\n }\n if (emitIntegrations) {\n for (const i of integrations) {\n const res = validateSkill(i);\n if (!res.ok) throw new Error(`Invalid integration ${i.name}: ${res.errors.join('; ')}`);\n }\n }\n await mkdir(targetDir, { recursive: true });\n let references = 0;\n const emitted: string[] = [];\n const integrationNames: string[] = [];\n const conflicts: SkillConflict[] = [];\n // Apply-to-all memory keyed by artifact CLASS. Skill emission shares one\n // decision across the run (a `noir sync` touching 8 skill files → 1 prompt)\n // when the resolver returns `{resolution, applyToAll: true}`.\n const memory = new Map<string, SkillConflictResolution>();\n const policy: 'overwrite' | 'preserve' = opts.conflictPolicy ?? 'overwrite';\n // Non-interactive guard. The bridge `NOIR_NON_INTERACTIVE` is set by the\n // bin's preAction under --json/--no-input; the explicit `interactive: false`\n // wins over the env (mirrors @noir-ai/create's hermetic flag). When false,\n // the resolver is NEVER consulted — differing files fall back to `policy`.\n const interactive =\n opts.interactive ??\n (process.env.NOIR_NON_INTERACTIVE === undefined || process.env.NOIR_NON_INTERACTIVE === '');\n\n /** Write a compiled skill file through the SAME conflict seam as\n * `regenerate`: read existing; if differs, consult the resolver (when\n * interactive + wired); record the conflict for the structured report. */\n const writeWithConflict = async (\n absDest: string,\n relDest: string,\n content: string,\n isReference: boolean,\n ): Promise<void> => {\n let existing: string | undefined;\n try {\n existing = await readFile(absDest, 'utf8');\n } catch {\n existing = undefined;\n }\n if (existing !== undefined && existing !== content) {\n const MODE = 'skill' as const;\n let resolution: SkillConflictResolution;\n const remembered = memory.get(MODE);\n if (remembered !== undefined) {\n resolution = remembered;\n } else if (interactive && opts.onConflict !== undefined) {\n const ctx: SkillConflictContext = {\n relPath: relDest,\n existing,\n proposed: content,\n mode: MODE,\n };\n const ret = await opts.onConflict(ctx);\n const unwrapped: SkillConflictResolution = typeof ret === 'string' ? ret : ret.resolution;\n if (typeof ret !== 'string' && ret.applyToAll === true) {\n memory.set(MODE, unwrapped);\n }\n resolution = unwrapped;\n } else {\n resolution = policy === 'preserve' ? 'preserve' : 'replace';\n }\n conflicts.push({\n path: relDest,\n mode: MODE,\n similarity: similarity(existing, content),\n existingSha: sha256Hex12(existing),\n proposedSha: sha256Hex12(content),\n resolution,\n });\n if (resolution === 'cancel') {\n throw new Error(`skill emit cancelled by user at ${relDest}`);\n }\n if (resolution === 'preserve') {\n // Skip this file; the user's bytes stand. Do not count as a reference.\n return;\n }\n if (resolution === 'rename') {\n // Move the user's file aside (unique suffix), then write the compiled bytes.\n await renameAside(absDest, '.local');\n } else if (resolution === 'duplicate') {\n // Write the compiled bytes to <absDest>.noir; keep the user's file.\n await writeFile(await uniqueAside(absDest, '.noir'), content, 'utf8');\n return;\n }\n // 'replace' → fall through and overwrite.\n }\n await writeFile(absDest, content, 'utf8');\n if (isReference) references++;\n };\n\n // Cursor skills land FLAT under targetDir (`.cursor/rules/<name>.mdc`) —\n // Cursor's rule loader scans `.cursor/rules/*.mdc` and does NOT recurse into\n // per-name subdirs. The verbatim branch (claude/agents-md/gemini/opencode)\n // keeps the canonical nested layout (`<name>/SKILL.md` + `<name>/references/`).\n const flat = target === 'cursor';\n for (const s of builtins) {\n const compiled = compileSkill(s, target);\n for (const f of compiled.files) {\n const dest = flat ? join(targetDir, ...f.path) : join(targetDir, s.name, ...f.path);\n await mkdir(dirname(dest), { recursive: true });\n const rel = flat ? f.path.join('/') : [s.name, ...f.path].join('/');\n await writeWithConflict(dest, rel, f.content, f.path[0] !== 'SKILL.md');\n }\n emitted.push(s.name);\n }\n if (emitIntegrations) {\n for (const i of integrations) {\n const compiled = compileIntegration(i, target);\n for (const f of compiled.files) {\n const dest = flat ? join(targetDir, ...f.path) : join(targetDir, i.name, ...f.path);\n await mkdir(dirname(dest), { recursive: true });\n const rel = flat ? f.path.join('/') : [i.name, ...f.path].join('/');\n await writeWithConflict(dest, rel, f.content, f.path[0] !== 'SKILL.md');\n }\n // `hostMcp` is NOT written per-skill — it is surfaced to the host adapter\n // (via discoverAll/compileIntegration) to merge into the host's single\n // MCP config (e.g. `.mcp.json`). Wiring lives in cli/daemon.\n emitted.push(i.name);\n integrationNames.push(i.name);\n }\n }\n\n // --- Prune stale `noir-*` entries from the managed namespace ----------\n // Idempotent hygiene: a previous Noir version may have shipped a builtin that\n // was since renamed/removed (e.g. `noir-old-thing`). Each `noir sync`\n // re-writes the CURRENT pack but a stale entry would otherwise linger forever.\n // After emit, scan `targetDir` for `noir-`-prefixed entries NOT in the emitted\n // set and remove them. ONLY the `noir-` namespace — user skills without the\n // prefix are NEVER touched (they are not Noir's to manage).\n //\n // `assertNotUserOwned` guard: a `noir-*` entry whose content does NOT\n // match the canonical Noir-emitted shape (SKILL.md with `name: noir-…`\n // frontmatter, OR a `.mdc` with the cursor frontmatter) is treated as\n // USER-AUTHORED and LEFT ALONE. A user hand-rolling `noir-myown/SKILL.md`\n // must never be silently deleted by a sync.\n //\n // Shape awareness: the nested layout (claude/agents-md/gemini/opencode)\n // writes one `noir-<name>/` DIR per skill → prune stale DIRS. The cursor flat\n // layout writes one `noir-<name>.mdc` FILE per skill → prune stale .mdc FILES.\n // Cursor ALSO clears legacy `noir-<name>/` dirs (nesting residue) so an\n // upgrade from nested→flat does not leave orphans under `.cursor/rules/`.\n const keep = new Set(emitted);\n const pruned: string[] = [];\n const preservedUserOwned: string[] = [];\n let dirEntries: import('node:fs').Dirent[] = [];\n try {\n dirEntries = await readdir(targetDir, { withFileTypes: true });\n } catch {\n dirEntries = []; // targetDir vanished between the mkdir above and now — nothing to prune.\n }\n for (const ent of dirEntries) {\n if (!ent.name.startsWith('noir-')) continue;\n if (flat) {\n // Cursor flat layout — prune stale FILES (`noir-<name>.mdc`) + legacy\n // nested DIRS (`noir-<name>/`) from a sync.\n if (ent.isFile()) {\n const mdcMatch = ent.name.match(/^(noir-[a-z0-9]+(?:-[a-z0-9]+)*)\\.mdc$/);\n const skillName = mdcMatch?.[1];\n if (skillName && keep.has(skillName)) continue;\n // `assertNotUserOwned`: a `.mdc` without canonical cursor\n // frontmatter (globs + alwaysApply) was hand-rolled by the user; skip.\n if (await isUserOwnedMdc(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n } else if (ent.isDirectory()) {\n // Legacy cursor nested dir — clear unconditionally (the flat\n // layout has NO `noir-{name}/` dirs under .cursor/rules/; any such dir\n // is stale by definition, regardless of name overlap with the current\n // pack — the fresh `.mdc` file is what's kept, not the dir).\n // Guard: skip if NONE of the `.mdc` files inside look Noir-emitted\n // (canonical cursor frontmatter) — a user hand-rolling a noir-X/ dir\n // with arbitrary content is preserved.\n if (await isUserOwnedCursorLegacyDir(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n } else {\n continue;\n }\n } else {\n // Nested layout — prune stale DIRS only (the canonical shape).\n if (!ent.isDirectory()) continue;\n if (keep.has(ent.name)) continue;\n // `assertNotUserOwned`: a `noir-*/` dir whose SKILL.md does not\n // carry canonical Noir frontmatter (`name: noir-…`) is user-authored.\n if (await isUserOwnedSkillDir(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n }\n // Best-effort removal: a failure to remove a stale entry must NOT fail the\n // emit (the fresh skills are already on disk and valid). Surface it via the\n // summary so a caller can warn; the next sync will try again.\n try {\n await rm(join(targetDir, ent.name), { recursive: true, force: true });\n pruned.push(ent.name);\n } catch {\n // Swallow — stale-entry pruning is hygienic, not correctness-critical.\n }\n }\n\n return {\n dir: targetDir,\n emitted,\n references,\n integrations: integrationNames,\n pruned,\n ...(preservedUserOwned.length > 0 ? { preservedUserOwned } : {}),\n ...(conflicts.length > 0 ? { conflicts } : {}),\n };\n}\n\n/** `assertNotUserOwned` for the nested layout: a `noir-{name}/` dir is\n * Noir-managed iff it contains a SKILL.md whose YAML frontmatter carries a\n * `name:` matching `noir-<dir>`. Missing SKILL.md, missing frontmatter, or a\n * non-matching name ⇒ user-authored (left alone). Best-effort: any IO error\n * ⇒ treat as user-owned (the safe default — never silently delete). */\nasync function isUserOwnedSkillDir(dirAbs: string): Promise<boolean> {\n try {\n const skillMd = await readFile(join(dirAbs, 'SKILL.md'), 'utf8');\n const m = skillMd.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (!m) return true; // no frontmatter at all → user-authored\n const fm = parseYaml(m[1] ?? '') as { name?: unknown };\n const dirName = basename(dirAbs);\n return typeof fm?.name !== 'string' || fm.name !== dirName;\n } catch {\n return true; // ENOENT (no SKILL.md) or parse error → user-authored (safe)\n }\n}\n\n/** `assertNotUserOwned` for the cursor flat layout: a `noir-*.mdc` file\n * is Noir-managed iff its YAML frontmatter carries `alwaysApply:` (the cursor\n * shape the compiler always emits). Missing frontmatter / no `alwaysApply` ⇒\n * user-authored. */\nasync function isUserOwnedMdc(fileAbs: string): Promise<boolean> {\n try {\n const md = await readFile(fileAbs, 'utf8');\n const m = md.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (!m) return true;\n const fm = parseYaml(m[1] ?? '') as { alwaysApply?: unknown };\n return fm?.alwaysApply === undefined;\n } catch {\n return true;\n }\n}\n\n/** `assertNotUserOwned` for the cursor flat-layout's legacy-nested-dir\n * case: a Noir sync left `noir-{name}/noir-{name}.mdc` behind. The dir\n * is Noir-managed iff at least one `.mdc` inside carries canonical cursor\n * frontmatter (`alwaysApply:`). Absent / hand-rolled content ⇒ user-authored.\n * Empty dir ⇒ treat as user-owned (safe — never silently delete). */\nasync function isUserOwnedCursorLegacyDir(dirAbs: string): Promise<boolean> {\n try {\n const entries = await readdir(dirAbs);\n if (entries.length === 0) return true;\n let sawCanonical = false;\n for (const name of entries) {\n if (!name.endsWith('.mdc')) continue;\n if (!(await isUserOwnedMdc(join(dirAbs, name)))) {\n sawCanonical = true;\n break;\n }\n }\n return !sawCanonical;\n } catch {\n return true; // ENOENT or unreadable → safe default (preserve)\n }\n}\n\n/** LCS similarity in [0,1]. Local copy (skills has no create dependency)\n * so the structured report's `similarity` matches the engine's algorithm. */\nfunction similarity(a: string, b: string): number {\n if (a === b) return 1;\n const A = a.split('\\n');\n const B = b.split('\\n');\n if (A.length === 0 && B.length === 0) return 1;\n const n = A.length;\n const m = B.length;\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0));\n for (let i = n - 1; i >= 0; i--) {\n const dpi = dp[i] ?? [];\n const dpi1 = dp[i + 1] ?? [];\n const ai = A[i] ?? '';\n for (let j = m - 1; j >= 0; j--) {\n const bj = B[j] ?? '';\n dpi[j] = ai === bj ? (dpi1[j + 1] ?? 0) + 1 : Math.max(dpi1[j] ?? 0, dpi[j + 1] ?? 0);\n }\n }\n const lcs = dp[0]?.[0] ?? 0;\n return n + m === 0 ? 1 : (2 * lcs) / (n + m);\n}\n\n/** Pick a fresh `<abs><suffix>` aside path that does NOT exist (mirrors\n * @noir-ai/create's `uniqueAside`). Used by the `rename`/`duplicate` resolutions\n * so the user's bytes never get silently clobbered. */\nasync function renameAside(abs: string, suffix: string): Promise<void> {\n const { rename } = await import('node:fs/promises');\n const dest = await uniqueAside(abs, suffix);\n await rename(abs, dest);\n}\n\nasync function uniqueAside(abs: string, suffix: string): Promise<string> {\n return uniqueAsideSync(abs, suffix);\n}\n\n// Convenience for callers/tests that already hold raw markdown (unused by emit path;\n// kept so adapters/tests can validate a single in-memory skill without a dir).\nexport { discoverAll, discoverBuiltin };\n","import { readdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseFrontmatter } from './compiler.js';\nimport { parseIntegration } from './integrations-schema.js';\nimport type { BuiltinSkill, IntegrationDeclaration, IntegrationSkill } from './types.js';\n\n// Package root = ONE level up from THIS module's directory. Works in every layout\n// because `builtin/` is always a sibling of the dir this code runs from:\n// - vitest: .../packages/skills/src/discover.ts -> HERE = src -> PKG_ROOT = package root\n// - built: .../packages/skills/dist/index.js -> HERE = dist -> PKG_ROOT = package root\n// (tsup bundles everything into dist/index.js; `dirname` still yields `dist`.)\nconst HERE = dirname(fileURLToPath(import.meta.url)); // .../src OR .../dist\nconst PKG_ROOT = dirname(HERE); // .../packages/skills\nexport const BUILTIN_DIR = join(PKG_ROOT, 'builtin');\nexport const INTEGRATIONS_DIR = join(PKG_ROOT, 'integrations');\n\n/** Read a `<name>/SKILL.md` + its optional `references/` directory. Shared by\n * builtin + integration discovery (both share the SKILL.md + references shape;\n * the difference is the `integration.json` declaration). */\nfunction readSkillMd(dir: string): Pick<BuiltinSkill, 'skillMd' | 'frontmatter' | 'references'> {\n const skillMd = readFileSync(join(dir, 'SKILL.md'), 'utf8');\n const frontmatter = parseFrontmatter(skillMd);\n const refsDir = join(dir, 'references');\n let references: BuiltinSkill['references'] = [];\n try {\n references = readdirSync(refsDir)\n .filter((f) => f.endsWith('.md'))\n .sort()\n .map((f) => ({ name: f, content: readFileSync(join(refsDir, f), 'utf8') }));\n } catch {\n references = []; // no references/ dir is fine\n }\n return { skillMd, frontmatter, references };\n}\n\nexport function discoverBuiltin(builtinDir: string = BUILTIN_DIR): BuiltinSkill[] {\n const dirs = readdirSync(builtinDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name.startsWith('noir-'))\n .map((e) => e.name)\n .sort();\n return dirs.map((name) => {\n const dir = join(builtinDir, name);\n return { name, dir, ...readSkillMd(dir) };\n });\n}\n\n/**\n * Discover every shipped integration under `integrations/<name>/` (sibling of\n * `builtin/`). Each entry is a builtin-shaped skill (validated `SKILL.md` +\n * optional references) PLUS a parsed + validated `integration.json`\n * declaration. The dir is resolved relative to the package root by default —\n * when `integrationsDir` is overridden (tests), only that dir is scanned.\n *\n * Throws an aggregate error if any integration is malformed (missing\n * `SKILL.md`, bad frontmatter, or an `integration.json` that fails the Zod\n * schema) — fail-fast, atomic-ish, mirroring the builtin compiler's posture.\n */\nexport function discoverIntegrations(\n integrationsDir: string = INTEGRATIONS_DIR,\n): IntegrationSkill[] {\n let entries: string[];\n try {\n entries = readdirSync(integrationsDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name.startsWith('noir-'))\n .map((e) => e.name)\n .sort();\n } catch {\n // No integrations/ dir (or unreadable) = no integrations shipped here.\n return [];\n }\n return entries.map((name) => {\n const dir = join(integrationsDir, name);\n const declarationRaw = JSON.parse(\n readFileSync(join(dir, 'integration.json'), 'utf8'),\n ) as unknown;\n let declaration: IntegrationDeclaration;\n try {\n declaration = parseIntegration(declarationRaw);\n } catch (err) {\n // ZodError → wrap with the integration's dir for an actionable message.\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Invalid integration.json in ${dir}: ${msg}`);\n }\n if (declaration.name !== name) {\n throw new Error(\n `Integration dir \"${name}\" must equal declaration name \"${declaration.name}\"`,\n );\n }\n return { name, dir, ...readSkillMd(dir), declaration, declarationRaw };\n });\n}\n\n/**\n * Discover the full shipped pack — builtins + integrations — in one call. Used\n * by `emitSkillsToDir` so `noir init`/`sync` emit BOTH (slice X goal). The\n * `integrationsDir` defaults to the sibling of `builtinDir` so a test fixture\n * that overrides only `builtinDir` cleanly resolves integrations to a\n * non-existent path (returns `[]`) instead of accidentally picking up the\n * shipped integrations.\n */\nexport function discoverAll(opts: { builtinDir?: string; integrationsDir?: string } = {}): {\n builtins: BuiltinSkill[];\n integrations: IntegrationSkill[];\n} {\n const builtinDir = opts.builtinDir ?? BUILTIN_DIR;\n const integrationsDir = opts.integrationsDir ?? join(dirname(builtinDir), 'integrations');\n return {\n builtins: discoverBuiltin(builtinDir),\n integrations: discoverIntegrations(integrationsDir),\n };\n}\n","// `integration.json` schema. Each integration ships a declaration\n// alongside its `SKILL.md` so the compiler can validate the auth/runtime/SDD\n// contract uniformly and so the host adapter knows whether to widen emission\n// (host MCP config) or stay skill-only.\n//\n// Doctrine (slice-x spec §architecture + v1x §4.4): skill-only by default;\n// `gated-write-proxy` for stateless writes routed through a Noir MCP tool\n// `mcp-stdio` for a Noir-spawned stateful runtime (deferred, gated on\n// keychain); `external-mcp` for a config-only pointer at a first-party/community\n// MCP server (emits a host `.mcp.json` server entry, no Noir runtime).\n//\n// Zod v4 (`z.record(keyType, valueType)` is key-first). The schema is additive\n// and defaults to the safest tier — every optional field degrades to \"skill\n// only, manual paste, no host MCP wiring\" — so a minimal `{ \"name\":\n// \"noir-x\" }` declaration still parses + behaves as a pure playbook.\n\nimport * as z from 'zod';\n\n/** Auth shape. Locked: `env-var` only until keychain lands (Q4b — refuse OAuth,\n * never silently lower the security bar). `fallback:'manual-paste'` keeps the\n * no-token path honest (the playbook tells the user to paste a value); `'none'`\n * is for integrations that genuinely do not need a token (read-only public\n * endpoints). */\nexport const IntegrationAuthSchema = z.object({\n type: z.literal('env-var'),\n tokenEnv: z.string().min(1),\n fallback: z.enum(['manual-paste', 'none']).default('manual-paste'),\n});\n\n/** SDD two-way binding. `intakeFrom` declares the external artifact kind the\n * `noir-brainstorming` skill pulls from (absorbed noir-intake in C3);\n * `writeBack` enumerates the fields `noir-wrap` pushes back at session end\n * (absorbed noir-document in C3). Strings (not enums) for `writeBack` so a\n * per-integration vocabulary stays expressible without\n * churning the schema. */\nexport const IntegrationSddSchema = z\n .object({\n intakeFrom: z.enum(['task', 'issue', 'none']).optional(),\n writeBack: z.array(z.string()).default([]),\n })\n .default({ writeBack: [] });\n\n/** Host MCP declaration. Only meaningful when `runtime ∈ {mcp-stdio,\n * external-mcp}`; `null` for `none`/`gated-write-proxy` (ClickUp — writes go\n * through Noir's own MCP tool, never the host's). */\nexport const IntegrationMcpSchema = z\n .object({\n command: z.string().min(1),\n transport: z.enum(['stdio', 'http']),\n args: z.array(z.string()).optional(),\n url: z.string().optional(),\n env: z.record(z.string(), z.string()).optional(),\n })\n .nullable()\n .default(null);\n\n/** Full `integration.json` declaration. `name` enforces the `noir-` prefix\n * shared with builtins so the unified pack reads consistently. */\nexport const IntegrationDeclarationSchema = z.object({\n name: z.string().regex(/^noir-[a-z0-9]+(?:-[a-z0-9]+)*$/, 'must match noir-<kebab>'),\n auth: IntegrationAuthSchema,\n runtime: z.enum(['none', 'gated-write-proxy', 'mcp-stdio', 'external-mcp']),\n sdd: IntegrationSddSchema,\n mcp: IntegrationMcpSchema,\n});\n\nexport type IntegrationDeclaration = z.infer<typeof IntegrationDeclarationSchema>;\n\n/** Coerce + validate an `integration.json` payload. Throws a ZodError on a\n * malformed declaration — the caller (discover) wraps it with the\n * integration's dir for an actionable message. */\nexport function parseIntegration(json: unknown): IntegrationDeclaration {\n return IntegrationDeclarationSchema.parse(json);\n}\n\n/** Non-throwing variant for the hygiene tests + lint helpers. */\nexport function validateIntegration(\n obj: unknown,\n): { ok: true; value: IntegrationDeclaration } | { ok: false; errors: string[] } {\n const parsed = IntegrationDeclarationSchema.safeParse(obj);\n if (parsed.success) return { ok: true, value: parsed.data };\n return { ok: false, errors: parsed.error.issues.map((i: { message: string }) => i.message) };\n}\n\n/** Runtime → host-MCP emission predicate. The compiler widens emission (makes\n * the host MCP block available to the adapter) ONLY for these tiers. The\n * adapter ultimately decides per-host what to render — for Claude, `external-\n * mcp` becomes a `.mcp.json` server entry; `mcp-stdio` registers through the\n * existing `noir mcp serve --stdio` entry so no NEW server entry is emitted. */\nexport function runtimeEmitsHostMcp(runtime: IntegrationDeclaration['runtime']): boolean {\n return runtime === 'mcp-stdio' || runtime === 'external-mcp';\n}\n","// Structural quality checks for the builtin skill pack — the C3 quality gate.\n//\n// Split from compiler.ts so `validateSkill` (hard errors) and `lintSkill` (soft\n// warnings) share one source of truth for the body-structure rules, and so the\n// rules are unit-testable in isolation. Canonical template (C3 spec §3):\n//\n// Overview → When to use → Procedure → Verification → Notes\n//\n// Rules here implement the researched canon (Anthropic best practices, the\n// agentskills.io spec, Claude Code docs): required sections, a <500-line body\n// budget, one-level-deep references (no chained refs), and a WHAT+WHEN\n// description. `checkRequiredSections` is the load-bearing check — it's what\n// forces every SKILL.md to carry a real playbook shape, not a shell.\n\nimport { ARTIFACT_TYPES } from '@noir-ai/core';\nimport type { BuiltinSkill } from './types.js';\n\n/** The max body length the canon recommends (Anthropic: \"under 500 lines\").\n * SKILL.md is a navigator, not a repository — split to references/ past this. */\nexport const MAX_BODY_LINES = 500;\n\n/** The floor for a full (non-stub) playbook body. Stubs were ~13 lines; a real\n * playbook needs at least this much substance to be loadable as guidance. */\nexport const MIN_FULL_BODY_LINES = 20;\n\n/** The canonical heading a skill MUST carry to describe its trigger conditions.\n * Accepts the C3 template spelling AND common variants so existing skills\n * aren't forced into a single casing. */\nconst WHEN_SECTION = /^## When to use$/im;\n\n/** The canonical heading a skill MUST carry for its numbered workflow. `## Steps`\n * is accepted for backward-compat with noir-wrap; `## Procedure` is canonical. */\nconst PROCEDURE_SECTION = /^## (Procedure|Steps)$/im;\n\n/** One of these closing sections must exist — evidence/notes/degradation. */\nconst CLOSING_SECTION = /^## (Verification|Notes|Fallbacks|Troubleshooting|Why order matters)$/im;\n\n/** The lead cue regex — the FIRST part of the description must be a trigger. */\nconst WHEN_CUE =\n /^(use|using|used|whenever|when|before|after|while|starting|encountering|completing|creating|about to|upon|during|to|for|on)\\b/i;\n\n/** A chained reference link — a reference file that links to ANOTHER reference\n * file (`references/a.md` → `references/b.md` or `../references/b.md`). The\n * canon forbids deep nesting (agentskills.io: \"no chained references\"). */\nconst CHAINED_REF_RE = /\\]\\((\\/?\\.?\\.?\\/)?(references\\/|\\.\\.\\/references\\/)/i;\n\n/**\n * Which required sections are missing from a skill body. Returns a list of\n * human-readable section names, e.g. `['## When to use', '## Verification']`.\n * Empty array = the body carries the canonical playbook shape.\n */\nexport function missingSections(body: string): string[] {\n const missing: string[] = [];\n if (!WHEN_SECTION.test(body)) missing.push('## When to use');\n if (!PROCEDURE_SECTION.test(body)) missing.push('## Procedure (or ## Steps)');\n if (!CLOSING_SECTION.test(body)) {\n missing.push('one of ## Verification / ## Notes / ## Fallbacks / ## Troubleshooting');\n }\n return missing;\n}\n\n/** True when the body is within the canon line budget. */\nexport function withinLineBudget(body: string, max: number = MAX_BODY_LINES): boolean {\n return body.split('\\n').length <= max;\n}\n\n/** Reference files that point at another reference file (chained — forbidden).\n * Returns the offending reference names. */\nexport function chainedReferences(skill: BuiltinSkill): string[] {\n return skill.references.filter((r) => CHAINED_REF_RE.test(r.content)).map((r) => r.name);\n}\n\n/**\n * True when the description carries BOTH a WHEN trigger lead AND a WHAT clause.\n * The C3 rule: `description` MUST lead with a WHEN cue (existing compiler rule)\n * AND contain a compact WHAT clause naming what the skill does.\n *\n * Two canonical shapes both pass:\n * \"Use when turning an idea into a spec — draft the spec.\" (WHEN → WHAT)\n * \"Use when a task writes back to ClickUp: update its status.\" (WHEN → WHAT)\n * The description MUST lead with a cue (the WHEN-only and WHAT-only schools are\n * both rejected). The WHAT clause is the part after the trigger phrase — split\n * on an em/en dash or a period that ends the trigger phrase. A real WHAT clause\n * has ≥3 words naming the action, so a bare \"Use when…\" fails.\n */\nexport function isWhatWhenDescription(description: string): boolean {\n const trimmed = description.trim();\n if (!trimmed) return false;\n if (!WHEN_CUE.test(trimmed)) return false; // must lead with a cue\n // Split off the trigger phrase: after an em/en dash, or after the first period.\n // \"Use when X — draft the spec.\" → whatPart = \"draft the spec.\"\n const whatPart = trimmed\n .split(/[—–]|(?<=\\.) /)\n .slice(1)\n .join(' ')\n .trim();\n return whatPart.split(/\\s+/).filter(Boolean).length >= 3;\n}\n\n/** True when the description leads with a WHEN cue (the existing rule, kept\n * here so validate + lint + hygiene share one implementation). */\nexport function looksLikeWhenDescription(description: string): boolean {\n return WHEN_CUE.test(description.trim());\n}\n\n/**\n * Soft-quality warnings for `lintSkill`. Each returns a short rule id + message.\n * These are advisory — a skill can pass `validateSkill` and still carry lint\n * warnings that the author should resolve. Rules are drawn from the researched\n * anti-pattern list (no examples, thin body, first/second person narration,\n * voodoo constants, time-sensitive version pins).\n */\nexport function lintWarnings(skill: BuiltinSkill): string[] {\n const warnings: string[] = [];\n const body = skill.skillMd; // linting the whole file (frontmatter + body) is fine for prose checks\n const bodyLines = body.split('\\n').length;\n\n if (bodyLines < MIN_FULL_BODY_LINES + 6) {\n // frontmatter is ~6 lines; a full playbook should clear 20 body lines\n warnings.push('thin-body: full playbook body is under 20 lines');\n }\n // Concrete examples: at least one fenced block or an \"example:\" mention.\n const hasExample = /```/.test(body) || /\\bexample:?\\b/i.test(body) || /\\be\\.g\\.\\b/i.test(body);\n if (!hasExample) {\n warnings.push('no-example: no concrete code fence or worked example in the body');\n }\n // First/second-person narration — \"I/we/you\" as the agent doing work.\n if (\n /\\b(I|we|you)\\s+(will|should|can|need|must|do|write|create|implement|run|build)\\b/i.test(body)\n ) {\n warnings.push('first-person: narration addresses the reader instead of imperative steps');\n }\n // Voodoo constants: a bare number with a magnitude claim but no justification\n // (\"wait 5 seconds\" without why). Very loose — catches \"wait N seconds\" without\n // a \"why\".\n if (\n /\\b(?:wait|sleep|retry|backoff|limit|cap)\\s+[a-z]*\\s*(\\d{1,4})\\b/i.test(body) &&\n !/because|to (avoid|prevent|give|let)/i.test(body)\n ) {\n warnings.push('voodoo-constant: numeric threshold without a stated reason');\n }\n // Time-sensitive version pins outside a Legacy section.\n const hasVersionPin = /as of \\d{4}|\\bversion \\d+\\.\\d+\\.\\d+\\b|\"v\\d+\\.\\d+\"/i.test(body);\n const hasLegacySection = /^## Legacy|^## Old patterns/i.test(body);\n if (hasVersionPin && !hasLegacySection) {\n warnings.push('time-sensitive: version/date pin outside a Legacy section');\n }\n return warnings;\n}\n\n/** `.noir/` subdirectories a skill may legitimately reference that are NOT\n * per-type artifact dirs (store db, audit export, rules, daemon state). */\nconst NON_ARTIFACT_DIRS = new Set(['store', 'audit', 'rules', 'state']);\n\n/** Canonical artifact directory → its type code(s) (from the C3 registry).\n * One directory can host multiple kinds (`subagents/` → BR + RP). */\nconst DIR_TO_CODES: ReadonlyMap<string, ReadonlySet<string>> = (() => {\n const m = new Map<string, Set<string>>();\n for (const t of Object.values(ARTIFACT_TYPES)) {\n const codes = m.get(t.dir);\n if (codes) codes.add(t.code);\n else m.set(t.dir, new Set([t.code]));\n }\n return m;\n})();\n\n/**\n * `.noir/…` output-path drift: a skill body or reference that names a `.noir/`\n * directory absent from the artifact registry (e.g. `.noir/sdd/`), or a file\n * under a canonical artifact directory whose name carries none of that\n * directory's type codes (e.g. `.noir/plans/<date>-<slug>.md` instead of\n * `PL-<NNNN>-…`). Hard errors — the host would mint non-standard artifacts.\n * See `docs/reference/artifact-format.md`.\n */\nexport function artifactPathDrift(skill: BuiltinSkill): string[] {\n const texts = [skill.skillMd, ...skill.references.map((r) => r.content)];\n const drifts = new Set<string>();\n // `.noir/<dir>/<name>` — name runs to whitespace/backtick/quote/paren so\n // `<date>-<slug>.md` placeholders are captured whole.\n const re = /\\.noir\\/([a-zA-Z0-9-]+)\\/([^\\s`'\"()]+)/g;\n for (const text of texts) {\n for (const m of text.matchAll(re)) {\n const dir = m[1];\n const name = m[2];\n if (!dir || !name) continue;\n const codes = DIR_TO_CODES.get(dir);\n if (codes === undefined) {\n if (!NON_ARTIFACT_DIRS.has(dir)) {\n drifts.add(`.noir/${dir}/ is not a canonical artifact directory (C3 artifact standard)`);\n }\n continue;\n }\n const expected = [...codes].join('/');\n if (![...codes].some((c) => name.startsWith(`${c}-`))) {\n drifts.add(`.noir/${dir}/${name} must be ${expected}-<NNNN>-… (C3 artifact standard)`);\n }\n }\n }\n return [...drifts];\n}\n","// Offline behavioral-eval harness for the skill pack — C3.\n//\n// Skills are declared as `evals/**/evals.json` per the agentskills.io format\n// (`{skill_name, evals:[{id, prompt, expected_output, assertions?}]}`). The\n// harness loads every suite and runs OFFLINE assertions only — `contains`,\n// `not-contains`, `regex`, `length-gte` — no LLM, no network, so it runs in\n// `pnpm test` (the project's offline/free constraint). This is the executable\n// that turns the JSON declaration into a vitest suite (see test/evals.test.ts).\n//\n// The evals are deliberately simple: they assert that a skill's PROMPT → a\n// simulated agent answer satisfies structural expectations. They are NOT\n// LLM-judge evals (deferred to a future slice, ADR'd); they are the CI-safe\n// baseline that catches regressions in the skill's core directives.\n\nimport { readdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/** A single offline assertion on the produced output. */\nexport type EvalAssertion =\n | { type: 'contains'; value: string }\n | { type: 'not-contains'; value: string }\n | { type: 'regex'; value: string }\n | { type: 'length-gte'; value: number };\n\n/** One eval case: a prompt + the expected directive the skill must produce. */\nexport interface SkillEval {\n id: string;\n prompt: string;\n expected_output: string;\n assertions?: EvalAssertion[];\n}\n\n/** A suite = one skill's evals (the `evals.json` shape). */\nexport interface EvalSuite {\n skill_name: string;\n evals: SkillEval[];\n}\n\n/** Validate an assertion's shape (fail-fast on a malformed evals.json). */\nfunction parseAssertion(a: unknown): EvalAssertion {\n if (typeof a !== 'object' || a === null) throw new Error('assertion must be an object');\n const { type, value } = a as { type?: unknown; value?: unknown };\n switch (type) {\n case 'contains':\n case 'not-contains':\n case 'regex':\n if (typeof value !== 'string' || value.length === 0) {\n throw new Error(`assertion '${String(type)}' requires a non-empty string value`);\n }\n return { type, value } as EvalAssertion;\n case 'length-gte':\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(\"assertion 'length-gte' requires a finite number value\");\n }\n return { type, value } as EvalAssertion;\n default:\n throw new Error(`unknown assertion type '${String(type)}'`);\n }\n}\n\n/** Parse + validate a raw `evals.json` payload. Throws on malformed shape. */\nexport function parseEvalSuite(raw: unknown): EvalSuite {\n if (typeof raw !== 'object' || raw === null) throw new Error('evals.json must be an object');\n const { skill_name, evals } = raw as { skill_name?: unknown; evals?: unknown };\n if (typeof skill_name !== 'string' || skill_name.length === 0) {\n throw new Error('evals.json requires a non-empty string `skill_name`');\n }\n if (!Array.isArray(evals) || evals.length === 0) {\n throw new Error(`evals.json for ${skill_name} requires a non-empty 'evals' array`);\n }\n const parsed: SkillEval[] = evals.map((e, i) => {\n if (typeof e !== 'object' || e === null) throw new Error(`eval[${i}] must be an object`);\n const { id, prompt, expected_output, assertions } = e as {\n id?: unknown;\n prompt?: unknown;\n expected_output?: unknown;\n assertions?: unknown;\n };\n if (typeof id !== 'string' || id.length === 0)\n throw new Error(`eval[${i}] requires a string id`);\n if (typeof prompt !== 'string' || prompt.length === 0) {\n throw new Error(`eval ${id} requires a string prompt`);\n }\n if (typeof expected_output !== 'string') {\n throw new Error(`eval ${id} requires a string expected_output`);\n }\n return {\n id,\n prompt,\n expected_output,\n assertions: Array.isArray(assertions) ? assertions.map(parseAssertion) : undefined,\n };\n });\n return { skill_name, evals: parsed };\n}\n\n/** Run a list of assertions against `output`. Returns pass + per-assertion failures. */\nexport function runAssertions(\n output: string,\n assertions: EvalAssertion[],\n): { pass: boolean; failures: string[] } {\n const failures: string[] = [];\n for (const a of assertions) {\n switch (a.type) {\n case 'contains':\n if (!output.includes(a.value)) failures.push(`expected to contain \"${a.value}\"`);\n break;\n case 'not-contains':\n if (output.includes(a.value)) failures.push(`expected NOT to contain \"${a.value}\"`);\n break;\n case 'regex':\n if (!new RegExp(a.value, 'i').test(output)) failures.push(`expected to match /${a.value}/`);\n break;\n case 'length-gte':\n if (output.length < a.value)\n failures.push(`expected length >= ${a.value} (got ${output.length})`);\n break;\n }\n }\n return { pass: failures.length === 0, failures };\n}\n\n// Package root (evals/ is a sibling of src/): resolve the evals dir the same\n// way discover.ts resolves builtin/. Works under vitest (src) + built (dist).\nconst HERE = dirname(fileURLToPath(import.meta.url));\nconst PKG_ROOT = dirname(HERE);\nexport const EVALS_DIR = join(PKG_ROOT, 'evals');\n\n/**\n * Load every eval suite under `dir` (default the shipped `evals/` dir — each\n * subdir carries an `evals.json`). Skips non-`.json` files and missing dirs\n * (no evals = no-op). Fail-fast on a malformed suite (a broken evals.json is a\n * bug, not a skip).\n */\nexport function loadEvalSuites(dir: string = EVALS_DIR): EvalSuite[] {\n let entries: string[];\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .sort();\n } catch {\n return []; // no evals/ dir (or unreadable) = no suites shipped\n }\n const suites: EvalSuite[] = [];\n for (const name of entries) {\n const file = join(dir, name, 'evals.json');\n try {\n const raw = JSON.parse(readFileSync(file, 'utf8')) as unknown;\n suites.push(parseEvalSuite(raw));\n } catch (err) {\n // Missing evals.json in a subdir is fine (it may be a scratch dir); a\n // malformed one is not. Distinguish by existence.\n try {\n readFileSync(file, 'utf8');\n throw err; // exists but malformed → rethrow\n } catch (re) {\n if (re === err) throw err;\n // ENOENT → skip silently.\n }\n }\n }\n return suites;\n}\n\n/** Evaluate one suite: for each eval, check its assertions against\n * `expected_output` (the directive the skill should produce). Returns pass/fail\n * per eval with the assertion failures. This is the offline core the vitest\n * runner drives. */\nexport function evaluateSuite(\n suite: EvalSuite,\n): Array<{ id: string; pass: boolean; failures: string[] }> {\n return suite.evals.map((e) => ({\n id: e.id,\n pass: e.assertions ? runAssertions(e.expected_output, e.assertions).pass : true,\n failures: e.assertions ? runAssertions(e.expected_output, e.assertions).failures : [],\n }));\n}\n","// Runtime-derived skill registry — C3.\n//\n// The registry is NOT a committed file: frontmatter is the single source of\n// truth and `buildRegistry()` derives a queryable index from `discoverAll()`\n// on demand (the CLI calls it; no drift risk, per the C3 decision D3). Every\n// field comes from either frontmatter (`metadata.category`, `metadata.version`)\n// or a shape check (`status` from the stub marker, `referenceCount`/`lines`\n// from the discovered files).\n\nimport { discoverAll } from './discover.js';\nimport type { BuiltinSkill } from './types.js';\n\n/** The pack-wide `noir-` namespace a registry entry always belongs to. */\nexport const NOIR_NAMESPACE = 'noir-';\n\n/** One row in the derived registry. */\nexport interface SkillRegistryEntry {\n /** Canonical `noir-<kebab>` id (== dir name). */\n name: string;\n /** `builtin` for the shipped pack; `integration` for an `integrations/<name>/`. */\n kind: 'builtin' | 'integration';\n /** Category from `metadata.category`; falls back to the `noir-`-stripped name\n * (a newly-authored skill still gets a sensible cell). */\n category: string;\n /** Per-skill version from `metadata.version`; defaults to `0.0.0` when absent\n * (the pack-level version in package.json is the authoritative release). */\n version: string;\n /** `full` when the body has no `> **Stub:**` marker; `stub` otherwise. */\n status: 'full' | 'stub';\n /** Lifecycle stage of the skill. `active` = currently shipped and usable;\n * `deprecated` = superseded (a renamed/merged predecessor). C3 spec §8.\n * The curated pack has no deprecated members today. */\n lifecycle: 'active' | 'deprecated';\n /** The WHAT+WHEN description (the trigger the host sees). */\n description: string;\n /** Number of `references/*.md` files. */\n referenceCount: number;\n /** Total SKILL.md line count (frontmatter + body). */\n lines: number;\n}\n\n/** Derive the category from the skill name when `metadata.category` is absent. */\nfunction fallbackCategory(name: string): string {\n return name.replace(/^noir-/, '') || 'general';\n}\n\n/** Map a discovered builtin/integration to a registry row. */\nfunction toEntry(s: BuiltinSkill, kind: 'builtin' | 'integration'): SkillRegistryEntry {\n const category = s.frontmatter.metadata?.category?.trim() || fallbackCategory(s.name);\n const version = s.frontmatter.metadata?.version?.trim() || '0.0.0';\n const status: 'full' | 'stub' = s.skillMd.includes('> **Stub:**') ? 'stub' : 'full';\n const lines = s.skillMd.split('\\n').length;\n return {\n name: s.name,\n kind,\n category,\n version,\n status,\n // Every shipped skill is active; `deprecated` is reserved for a future\n // superseded member (C3 spec §8).\n lifecycle: 'active' as const,\n description: typeof s.frontmatter.description === 'string' ? s.frontmatter.description : '',\n referenceCount: s.references.length,\n lines,\n };\n}\n\n/**\n * Build the full registry from `discoverAll()` — builtins + integrations in\n * one list, sorted by name. Pure (no I/O beyond discovery); never throws for\n * a malformed skill (a discovery failure surfaces upstream, the same as the\n * compiler's fail-fast).\n */\nexport function buildRegistry(): SkillRegistryEntry[] {\n const { builtins, integrations } = discoverAll();\n return [\n ...builtins.map((b) => toEntry(b, 'builtin')),\n ...integrations.map((i) => toEntry(i, 'integration')),\n ].sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/** Convenience: registry filtered to a single category (CLI grouping). */\nexport function registryByCategory(category: string): SkillRegistryEntry[] {\n return buildRegistry().filter((e) => e.category === category);\n}\n","// Tokens that must NOT survive a port (predecessor plugin internals + Superpowers\n// rhetoric). Shared by the compiler's lint helper and the hygiene tests (T5).\nexport const FORBIDDEN_RESIDUE: readonly string[] = [\n 'workflow/<task', // predecessor state file\n 'noir-workflow.mode', // predecessor mode flag\n 'noir-workflow', // predecessor plugin name (as a plugin/path reference)\n '@uiigateway', // predecessor Angular specifics\n // NOTE: 'ClickUp'/'clickup' were forbidden during the predecessor-port era (the\n // ClickUp REST precedent lived in the deleted noir-workflow plugin). ClickUp is\n // now reintroduced as a first-class Noir integration (skills/integrations/noir-clickup),\n // so the token is no longer residue — it is the integration's legitimate subject.\n '<EXTREMELY-IMPORTANT', // Superpowers rhetoric\n 'SUBAGENT-STOP', // Superpowers rhetoric\n 'plugins/noir-workflow', // predecessor path\n];\n"],"mappings":"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","names":["dirname","join","join","dirname","readdirSync","readFileSync","dirname","join","fileURLToPath","HERE","PKG_ROOT"]}
1
+ {"version":3,"sources":["../src/compiler.ts","../src/discover.ts","../src/integrations-schema.ts","../src/quality.ts","../src/evals.ts","../src/registry.ts","../src/residue.ts"],"sourcesContent":["import { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { sha256Hex12, uniqueAsideSync } from '@noir-ai/core';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport { discoverAll, discoverBuiltin } from './discover.js';\nimport { runtimeEmitsHostMcp } from './integrations-schema.js';\nimport {\n artifactPathDrift,\n chainedReferences,\n isWhatWhenDescription,\n lintWarnings,\n looksLikeWhenDescription,\n MAX_BODY_LINES,\n missingSections,\n withinLineBudget,\n} from './quality.js';\n\n// Re-exported for backward-compat — callers (hygiene tests, CLI) import\n// `looksLikeWhenDescription` from './compiler.js'. The single source of truth\n// is quality.ts; this alias keeps the old import surface stable.\nexport { looksLikeWhenDescription } from './quality.js';\n\nimport type {\n BuiltinSkill,\n CompiledIntegration,\n CompiledSkill,\n CompileTarget,\n EmitSummary,\n IntegrationSkill,\n SkillConflict,\n SkillConflictContext,\n SkillConflictResolution,\n SkillConflictResolver,\n SkillFrontmatter,\n ValidationResult,\n} from './types.js';\n\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/;\nconst NAME_RE = /^noir-[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst MAX_DESC = 1024;\n\nexport function parseFrontmatter(md: string): SkillFrontmatter {\n const m = md.match(FRONTMATTER_RE);\n if (!m) throw new Error('Skill missing YAML frontmatter (expected --- ... ---)');\n // Group 1 always exists when the regex matches; guard past noUncheckedIndexedAccess.\n const yaml = m[1];\n if (yaml === undefined) throw new Error('Skill missing YAML frontmatter (expected --- ... ---)');\n const fm = parseYaml(yaml) as SkillFrontmatter;\n if (typeof fm?.name !== 'string' || typeof fm?.description !== 'string') {\n throw new Error('Skill frontmatter requires string `name` + `description`');\n }\n return fm;\n}\n\nexport function bodyOf(md: string): string {\n return md.replace(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?/, '');\n}\n\nexport function validateSkill(skill: BuiltinSkill): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n const { name, description, metadata } = skill.frontmatter;\n if (!name) errors.push('missing `name`');\n else if (!NAME_RE.test(name)) errors.push(`name \"${name}\" must match noir-<kebab>`);\n if (basename(skill.dir) !== name) {\n errors.push(`dir \"${basename(skill.dir)}\" must equal name \"${name}\"`);\n }\n if (!description?.trim()) errors.push('missing `description`');\n else if (description.length > MAX_DESC) errors.push(`description exceeds ${MAX_DESC} chars`);\n else if (!looksLikeWhenDescription(description)) {\n errors.push('description must state WHEN to trigger (e.g. \"Use when…\"), not WHAT it does');\n } else if (!isWhatWhenDescription(description)) {\n // The description must ALSO carry a WHAT clause — the trigger phrase\n // alone (WHEN-only) fails. Errors, not warnings: it's part of the contract.\n errors.push('description must be WHAT+WHEN — a WHAT clause after the trigger phrase');\n }\n // Structural gate: metadata, required sections, line budget, one-level refs.\n if (!metadata?.category?.trim()) errors.push('missing `metadata.category`');\n if (!metadata?.version?.trim()) errors.push('missing `metadata.version`');\n const body = bodyOf(skill.skillMd);\n const missing = missingSections(body);\n for (const sec of missing) errors.push(`missing required section: ${sec}`);\n if (!withinLineBudget(body)) {\n errors.push(`body exceeds ${MAX_BODY_LINES}-line budget (${body.split('\\n').length} lines)`);\n }\n const chained = chainedReferences(skill);\n for (const r of chained)\n errors.push(`reference \"${r}\" chains to another reference (must be one level deep)`);\n for (const r of skill.references) {\n if (!/^[a-z0-9-]+\\.md$/i.test(r.name)) errors.push(`reference \"${r.name}\" must be <kebab>.md`);\n if (!r.content.trim()) errors.push(`reference \"${r.name}\" is empty`);\n }\n // Generated-artifact standard: no `.noir/` output-path drift in the body\n // or references (unknown dir, or a filename missing its type-code prefix).\n for (const d of artifactPathDrift(skill)) errors.push(d);\n // Soft warnings (lint-level) — advisory, non-failing.\n warnings.push(...lintWarnings(skill));\n return { ok: errors.length === 0, errors, warnings: warnings.length > 0 ? warnings : undefined };\n}\n\n/**\n * `lintSkill` — the soft quality gate. Errors = `validateSkill` errors (a\n * skill that fails validation is broken); warnings = `quality.ts` style rules\n * (thin body, no examples, first-person narration, …). A skill can validate\n * clean yet still carry lint warnings the author should resolve.\n */\nexport function lintSkill(skill: BuiltinSkill): {\n name: string;\n errors: string[];\n warnings: string[];\n} {\n const res = validateSkill(skill);\n return { name: skill.name, errors: res.errors, warnings: res.warnings ?? [] };\n}\n\n/**\n * Compile a builtin skill into the host-shaped `CompiledSkill`. The validator\n * runs for every target (a malformed skill is malformed in any format).\n *\n * - `claude` | `agents-md` | `gemini` | `opencode` (the AGENTS.md-aligned\n * hosts): canonical format copied verbatim — `SKILL.md` plus its\n * `references/` siblings. These hosts read the same SKILL.md shape;\n * the EMIT-LOCATION (which dir they land in) is the cli/adapter's job, not\n * the compiler's.\n * - `cursor`: transform into a Cursor `.mdc` rule — ONE file `<name>.mdc`\n * whose frontmatter carries the skill description, a wildcard globs entry,\n * and `alwaysApply: false` (the agent decides whether to pull the rule via\n * the description; never auto-applied). The body is the SKILL.md BODY\n * (frontmatter stripped). Cursor's rule format has no references concept,\n * so references are dropped (documented as a known limitation — the body is\n * the surface).\n *\n * The `target` defaults to `'claude'` for backward compatibility with every\n * existing caller; the verbatim branch produces byte-identical output to v1.1\n * (the regression anchor).\n */\nexport function compileSkill(skill: BuiltinSkill, target: CompileTarget = 'claude'): CompiledSkill {\n const res = validateSkill(skill);\n if (!res.ok) throw new Error(`Cannot compile ${skill.name}: ${res.errors.join('; ')}`);\n if (target === 'cursor') {\n return { name: skill.name, files: [{ path: [`${skill.name}.mdc`], content: toMdc(skill) }] };\n }\n // claude / agents-md / gemini / opencode → canonical verbatim format.\n const files = [\n { path: ['SKILL.md'], content: skill.skillMd },\n ...skill.references.map((r) => ({ path: ['references', r.name], content: r.content })),\n ];\n return { name: skill.name, files };\n}\n\n/**\n * Cursor `.mdc` rule transform — frontmatter `{description, globs, alwaysApply:\n * false}` + the SKILL.md body. The description drives Cursor's agent-decided\n * rule selection (the default: `alwaysApply: false`).\n * Cursor's rule format has no references/ concept, so the body alone is the\n * surface; references are dropped (documented risk in the spec).\n *\n * Frontmatter is YAML-serialized via the `yaml` package (already a dep) so\n * description escaping (colons, quotes, multi-line) is correct without hand\n * rolling. `lineWidth: 0` keeps descriptions on a single quoted line (predictable\n * byte shape; Cursor tolerates either). */\nfunction toMdc(skill: BuiltinSkill): string {\n const frontmatter = stringifyYaml(\n {\n description: skill.frontmatter.description,\n globs: ['**/*'],\n alwaysApply: false,\n },\n { lineWidth: 0 },\n ).trimEnd();\n const body = bodyOf(skill.skillMd);\n // Cursor's `.mdc` shape: leading `---\\n`, frontmatter, closing `---\\n`, blank\n // line, body. Trailing newline so files concatenate cleanly on disk.\n return `---\\n${frontmatter}\\n---\\n${body.endsWith('\\n') ? body : `${body}\\n`}`;\n}\n\n/**\n * Compile an integration: validates the SKILL.md (same shape as builtins) +\n * renders the same per-skill files. When the integration's `runtime` widens\n * emission (`mcp-stdio`/`external-mcp`) AND it carries a non-null `mcp`\n * declaration, also expose a provider-neutral `hostMcp` server block the host\n * adapter may merge into the host MCP config (e.g. Claude's `.mcp.json`).\n *\n * For `gated-write-proxy` (ClickUp) + `none` → no `hostMcp` (writes route\n * through Noir's own MCP tool; the host MCP config is unchanged). For\n * `mcp-stdio` the hostMcp is exposed (the compiler widens emission); the\n * Claude adapter still does not emit a NEW server entry for it (Noir's own\n * `noir mcp serve` entry already covers it).\n */\nexport function compileIntegration(\n integration: IntegrationSkill,\n target: CompileTarget = 'claude',\n): CompiledIntegration {\n // Validate the skill half first (throws on a bad SKILL.md — same path as\n // builtins). compileSkill returns a base CompiledSkill; we layer hostMcp on.\n const base = compileSkill(integration, target);\n const { runtime, mcp } = integration.declaration;\n if (runtimeEmitsHostMcp(runtime) && mcp !== null) {\n return {\n ...base,\n hostMcp: {\n serverName: integration.name,\n command: mcp.command,\n args: mcp.args,\n transport: mcp.transport,\n url: mcp.url,\n env: mcp.env,\n },\n };\n }\n return base;\n}\n\nexport async function emitSkillsToDir(\n targetDir: string,\n opts: {\n builtinDir?: string;\n integrationsDir?: string;\n includeIntegrations?: boolean;\n /** CompileTarget — selects the per-skill transform. Defaults to\n * `'claude'` (the v1.1 verbatim SKILL.md shape) so every existing caller\n * stays byte-identical. `'cursor'` compiles each skill to a `.mdc` rule\n * file (frontmatter `description`/`globs`/`alwaysApply:false` + the\n * SKILL.md body); the other targets map to the verbatim shape (their\n * hosts have no skill concept, so emit is skipped upstream — but the\n * default-verbatim policy keeps the signature total). */\n target?: CompileTarget;\n /** Policy for a skill file that exists AND differs from the compiled\n * bytes, when no {@link onConflict} resolver is wired (or non-interactive).\n * Default `'overwrite'` preserves the v1.2 behavior (every sync clobbers).\n * `'preserve'` skips the differing file (the CI / non-TTY default the CLI\n * threads via `buildConflictOpts`). */\n conflictPolicy?: 'overwrite' | 'preserve';\n /** Per-file conflict resolver. The CLI passes its single\n * `buildConflictOpts().onConflict` (clack menu + diff preview + apply-to-\n * all) through unchanged; structurally compatible with @noir-ai/create's\n * `ConflictResolverReturn`. */\n onConflict?: SkillConflictResolver;\n /** Explicit interactivity flag (the engine reads THIS, not process.env,\n * matching @noir-ai/create's hermetic-interactive contract). When `false`,\n * the resolver is NEVER consulted (CI / --json never hangs on a prompt). */\n interactive?: boolean;\n } = {},\n): Promise<EmitSummary> {\n const target: CompileTarget = opts.target ?? 'claude';\n const { builtins, integrations } = discoverAll({\n builtinDir: opts.builtinDir,\n integrationsDir: opts.integrationsDir,\n });\n const emitIntegrations = opts.includeIntegrations ?? true;\n // Validate the whole pack before writing anything (fail-fast, atomic-ish).\n for (const s of builtins) {\n const res = validateSkill(s);\n if (!res.ok) throw new Error(`Invalid builtin skill ${s.name}: ${res.errors.join('; ')}`);\n }\n if (emitIntegrations) {\n for (const i of integrations) {\n const res = validateSkill(i);\n if (!res.ok) throw new Error(`Invalid integration ${i.name}: ${res.errors.join('; ')}`);\n }\n }\n await mkdir(targetDir, { recursive: true });\n let references = 0;\n const emitted: string[] = [];\n const preserved: string[] = [];\n // Every skill this run is responsible for, whether or not its bytes landed.\n // The prune step below keeps these: a preserved skill's files are the user's\n // own, and deleting the directory they live in is the one outcome pruning\n // must never produce.\n const kept: string[] = [];\n const integrationNames: string[] = [];\n const conflicts: SkillConflict[] = [];\n // Apply-to-all memory keyed by artifact CLASS. Skill emission shares one\n // decision across the run (a `noir sync` touching 8 skill files → 1 prompt)\n // when the resolver returns `{resolution, applyToAll: true}`.\n const memory = new Map<string, SkillConflictResolution>();\n const policy: 'overwrite' | 'preserve' = opts.conflictPolicy ?? 'overwrite';\n // Non-interactive guard. The bridge `NOIR_NON_INTERACTIVE` is set by the\n // bin's preAction under --json/--no-input; the explicit `interactive: false`\n // wins over the env (mirrors @noir-ai/create's hermetic flag). When false,\n // the resolver is NEVER consulted — differing files fall back to `policy`.\n const interactive =\n opts.interactive ??\n (process.env.NOIR_NON_INTERACTIVE === undefined || process.env.NOIR_NON_INTERACTIVE === '');\n\n /** Write a compiled skill file through the SAME conflict seam as\n * `regenerate`: read existing; if differs, consult the resolver (when\n * interactive + wired); record the conflict for the structured report.\n *\n * Returns whether the compiled bytes actually reached a Noir-owned path.\n * `'preserved'` means the differing copy on disk stands and nothing was\n * written for it — the caller may not count that file as refreshed. */\n const writeWithConflict = async (\n absDest: string,\n relDest: string,\n content: string,\n isReference: boolean,\n ): Promise<'written' | 'preserved'> => {\n let existing: string | undefined;\n try {\n existing = await readFile(absDest, 'utf8');\n } catch {\n existing = undefined;\n }\n if (existing !== undefined && existing !== content) {\n const MODE = 'skill' as const;\n let resolution: SkillConflictResolution;\n const remembered = memory.get(MODE);\n if (remembered !== undefined) {\n resolution = remembered;\n } else if (interactive && opts.onConflict !== undefined) {\n const ctx: SkillConflictContext = {\n relPath: relDest,\n existing,\n proposed: content,\n mode: MODE,\n };\n const ret = await opts.onConflict(ctx);\n const unwrapped: SkillConflictResolution = typeof ret === 'string' ? ret : ret.resolution;\n if (typeof ret !== 'string' && ret.applyToAll === true) {\n memory.set(MODE, unwrapped);\n }\n resolution = unwrapped;\n } else {\n resolution = policy === 'preserve' ? 'preserve' : 'replace';\n }\n conflicts.push({\n path: relDest,\n mode: MODE,\n similarity: similarity(existing, content),\n existingSha: sha256Hex12(existing),\n proposedSha: sha256Hex12(content),\n resolution,\n });\n if (resolution === 'cancel') {\n throw new Error(`skill emit cancelled by user at ${relDest}`);\n }\n if (resolution === 'preserve') {\n // Skip this file; the user's bytes stand. Do not count as a reference.\n return 'preserved';\n }\n if (resolution === 'rename') {\n // Move the user's file aside (unique suffix), then write the compiled bytes.\n await renameAside(absDest, '.local');\n } else if (resolution === 'duplicate') {\n // Write the compiled bytes to <absDest>.noir; keep the user's file.\n // The compiled version is on disk (just not at the canonical path), so\n // this counts as written — the user explicitly asked to keep theirs.\n await writeFile(await uniqueAside(absDest, '.noir'), content, 'utf8');\n return 'written';\n }\n // 'replace' → fall through and overwrite.\n }\n await writeFile(absDest, content, 'utf8');\n if (isReference) references++;\n return 'written';\n };\n\n // Cursor skills land FLAT under targetDir (`.cursor/rules/<name>.mdc`) —\n // Cursor's rule loader scans `.cursor/rules/*.mdc` and does NOT recurse into\n // per-name subdirs. The verbatim branch (claude/agents-md/gemini/opencode)\n // keeps the canonical nested layout (`<name>/SKILL.md` + `<name>/references/`).\n const flat = target === 'cursor';\n // Partition by outcome, not by attempt: a skill lands in `emitted` only when\n // every one of its files is now Noir's bytes, and in `preserved` when at\n // least one file was left as the user had it. A skill is never in both, and\n // a half-refreshed skill is reported as preserved — under-reporting stale\n // bytes is the failure this split exists to prevent.\n for (const s of builtins) {\n const compiled = compileSkill(s, target);\n let sawPreserved = false;\n for (const f of compiled.files) {\n const dest = flat ? join(targetDir, ...f.path) : join(targetDir, s.name, ...f.path);\n await mkdir(dirname(dest), { recursive: true });\n const rel = flat ? f.path.join('/') : [s.name, ...f.path].join('/');\n const outcome = await writeWithConflict(dest, rel, f.content, f.path[0] !== 'SKILL.md');\n if (outcome === 'preserved') sawPreserved = true;\n }\n kept.push(s.name);\n if (sawPreserved) preserved.push(s.name);\n else emitted.push(s.name);\n }\n if (emitIntegrations) {\n for (const i of integrations) {\n const compiled = compileIntegration(i, target);\n let sawPreserved = false;\n for (const f of compiled.files) {\n const dest = flat ? join(targetDir, ...f.path) : join(targetDir, i.name, ...f.path);\n await mkdir(dirname(dest), { recursive: true });\n const rel = flat ? f.path.join('/') : [i.name, ...f.path].join('/');\n const outcome = await writeWithConflict(dest, rel, f.content, f.path[0] !== 'SKILL.md');\n if (outcome === 'preserved') sawPreserved = true;\n }\n // `hostMcp` is NOT written per-skill — it is surfaced to the host adapter\n // (via discoverAll/compileIntegration) to merge into the host's single\n // MCP config (e.g. `.mcp.json`). Wiring lives in cli/daemon.\n kept.push(i.name);\n if (sawPreserved) preserved.push(i.name);\n else emitted.push(i.name);\n integrationNames.push(i.name);\n }\n }\n\n // --- Prune stale `noir-*` entries from the managed namespace ----------\n // Idempotent hygiene: a previous Noir version may have shipped a builtin that\n // was since renamed/removed (e.g. `noir-old-thing`). Each `noir sync`\n // re-writes the CURRENT pack but a stale entry would otherwise linger forever.\n // After emit, scan `targetDir` for `noir-`-prefixed entries this run did not\n // account for and remove them. ONLY the `noir-` namespace — user skills\n // without the prefix are NEVER touched (they are not Noir's to manage).\n // \"Accounted for\" spans the whole pack, not just the written half: a skill\n // whose files were preserved still owns its directory.\n //\n // `assertNotUserOwned` guard: a `noir-*` entry whose content does NOT\n // match the canonical Noir-emitted shape (SKILL.md with `name: noir-…`\n // frontmatter, OR a `.mdc` with the cursor frontmatter) is treated as\n // USER-AUTHORED and LEFT ALONE. A user hand-rolling `noir-myown/SKILL.md`\n // must never be silently deleted by a sync.\n //\n // Shape awareness: the nested layout (claude/agents-md/gemini/opencode)\n // writes one `noir-<name>/` DIR per skill → prune stale DIRS. The cursor flat\n // layout writes one `noir-<name>.mdc` FILE per skill → prune stale .mdc FILES.\n // Cursor ALSO clears legacy `noir-<name>/` dirs (nesting residue) so an\n // upgrade from nested→flat does not leave orphans under `.cursor/rules/`.\n const keep = new Set(kept);\n const pruned: string[] = [];\n const preservedUserOwned: string[] = [];\n let dirEntries: import('node:fs').Dirent[] = [];\n try {\n dirEntries = await readdir(targetDir, { withFileTypes: true });\n } catch {\n dirEntries = []; // targetDir vanished between the mkdir above and now — nothing to prune.\n }\n for (const ent of dirEntries) {\n if (!ent.name.startsWith('noir-')) continue;\n if (flat) {\n // Cursor flat layout — prune stale FILES (`noir-<name>.mdc`) + legacy\n // nested DIRS (`noir-<name>/`) from a sync.\n if (ent.isFile()) {\n const mdcMatch = ent.name.match(/^(noir-[a-z0-9]+(?:-[a-z0-9]+)*)\\.mdc$/);\n const skillName = mdcMatch?.[1];\n if (skillName && keep.has(skillName)) continue;\n // `assertNotUserOwned`: a `.mdc` without canonical cursor\n // frontmatter (globs + alwaysApply) was hand-rolled by the user; skip.\n if (await isUserOwnedMdc(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n } else if (ent.isDirectory()) {\n // Legacy cursor nested dir — clear unconditionally (the flat\n // layout has NO `noir-{name}/` dirs under .cursor/rules/; any such dir\n // is stale by definition, regardless of name overlap with the current\n // pack — the fresh `.mdc` file is what's kept, not the dir).\n // Guard: skip if NONE of the `.mdc` files inside look Noir-emitted\n // (canonical cursor frontmatter) — a user hand-rolling a noir-X/ dir\n // with arbitrary content is preserved.\n if (await isUserOwnedCursorLegacyDir(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n } else {\n continue;\n }\n } else {\n // Nested layout — prune stale DIRS only (the canonical shape).\n if (!ent.isDirectory()) continue;\n if (keep.has(ent.name)) continue;\n // `assertNotUserOwned`: a `noir-*/` dir whose SKILL.md does not\n // carry canonical Noir frontmatter (`name: noir-…`) is user-authored.\n if (await isUserOwnedSkillDir(join(targetDir, ent.name))) {\n preservedUserOwned.push(ent.name);\n continue;\n }\n }\n // Best-effort removal: a failure to remove a stale entry must NOT fail the\n // emit (the fresh skills are already on disk and valid). Surface it via the\n // summary so a caller can warn; the next sync will try again.\n try {\n await rm(join(targetDir, ent.name), { recursive: true, force: true });\n pruned.push(ent.name);\n } catch {\n // Swallow — stale-entry pruning is hygienic, not correctness-critical.\n }\n }\n\n return {\n dir: targetDir,\n emitted,\n references,\n integrations: integrationNames,\n preserved,\n pruned,\n ...(preservedUserOwned.length > 0 ? { preservedUserOwned } : {}),\n ...(conflicts.length > 0 ? { conflicts } : {}),\n };\n}\n\n/** `assertNotUserOwned` for the nested layout: a `noir-{name}/` dir is\n * Noir-managed iff it contains a SKILL.md whose YAML frontmatter carries a\n * `name:` matching `noir-<dir>`. Missing SKILL.md, missing frontmatter, or a\n * non-matching name ⇒ user-authored (left alone). Best-effort: any IO error\n * ⇒ treat as user-owned (the safe default — never silently delete). */\nasync function isUserOwnedSkillDir(dirAbs: string): Promise<boolean> {\n try {\n const skillMd = await readFile(join(dirAbs, 'SKILL.md'), 'utf8');\n const m = skillMd.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (!m) return true; // no frontmatter at all → user-authored\n const fm = parseYaml(m[1] ?? '') as { name?: unknown };\n const dirName = basename(dirAbs);\n return typeof fm?.name !== 'string' || fm.name !== dirName;\n } catch {\n return true; // ENOENT (no SKILL.md) or parse error → user-authored (safe)\n }\n}\n\n/** `assertNotUserOwned` for the cursor flat layout: a `noir-*.mdc` file\n * is Noir-managed iff its YAML frontmatter carries `alwaysApply:` (the cursor\n * shape the compiler always emits). Missing frontmatter / no `alwaysApply` ⇒\n * user-authored. */\nasync function isUserOwnedMdc(fileAbs: string): Promise<boolean> {\n try {\n const md = await readFile(fileAbs, 'utf8');\n const m = md.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n if (!m) return true;\n const fm = parseYaml(m[1] ?? '') as { alwaysApply?: unknown };\n return fm?.alwaysApply === undefined;\n } catch {\n return true;\n }\n}\n\n/** `assertNotUserOwned` for the cursor flat-layout's legacy-nested-dir\n * case: a Noir sync left `noir-{name}/noir-{name}.mdc` behind. The dir\n * is Noir-managed iff at least one `.mdc` inside carries canonical cursor\n * frontmatter (`alwaysApply:`). Absent / hand-rolled content ⇒ user-authored.\n * Empty dir ⇒ treat as user-owned (safe — never silently delete). */\nasync function isUserOwnedCursorLegacyDir(dirAbs: string): Promise<boolean> {\n try {\n const entries = await readdir(dirAbs);\n if (entries.length === 0) return true;\n let sawCanonical = false;\n for (const name of entries) {\n if (!name.endsWith('.mdc')) continue;\n if (!(await isUserOwnedMdc(join(dirAbs, name)))) {\n sawCanonical = true;\n break;\n }\n }\n return !sawCanonical;\n } catch {\n return true; // ENOENT or unreadable → safe default (preserve)\n }\n}\n\n/** LCS similarity in [0,1]. Local copy (skills has no create dependency)\n * so the structured report's `similarity` matches the engine's algorithm. */\nfunction similarity(a: string, b: string): number {\n if (a === b) return 1;\n const A = a.split('\\n');\n const B = b.split('\\n');\n if (A.length === 0 && B.length === 0) return 1;\n const n = A.length;\n const m = B.length;\n const dp: number[][] = Array.from({ length: n + 1 }, () => new Array<number>(m + 1).fill(0));\n for (let i = n - 1; i >= 0; i--) {\n const dpi = dp[i] ?? [];\n const dpi1 = dp[i + 1] ?? [];\n const ai = A[i] ?? '';\n for (let j = m - 1; j >= 0; j--) {\n const bj = B[j] ?? '';\n dpi[j] = ai === bj ? (dpi1[j + 1] ?? 0) + 1 : Math.max(dpi1[j] ?? 0, dpi[j + 1] ?? 0);\n }\n }\n const lcs = dp[0]?.[0] ?? 0;\n return n + m === 0 ? 1 : (2 * lcs) / (n + m);\n}\n\n/** Pick a fresh `<abs><suffix>` aside path that does NOT exist (mirrors\n * @noir-ai/create's `uniqueAside`). Used by the `rename`/`duplicate` resolutions\n * so the user's bytes never get silently clobbered. */\nasync function renameAside(abs: string, suffix: string): Promise<void> {\n const { rename } = await import('node:fs/promises');\n const dest = await uniqueAside(abs, suffix);\n await rename(abs, dest);\n}\n\nasync function uniqueAside(abs: string, suffix: string): Promise<string> {\n return uniqueAsideSync(abs, suffix);\n}\n\n// Convenience for callers/tests that already hold raw markdown (unused by emit path;\n// kept so adapters/tests can validate a single in-memory skill without a dir).\nexport { discoverAll, discoverBuiltin };\n","import { readdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { parseFrontmatter } from './compiler.js';\nimport { parseIntegration } from './integrations-schema.js';\nimport type { BuiltinSkill, IntegrationDeclaration, IntegrationSkill } from './types.js';\n\n// Package root = ONE level up from THIS module's directory. Works in every layout\n// because `builtin/` is always a sibling of the dir this code runs from:\n// - vitest: .../packages/skills/src/discover.ts -> HERE = src -> PKG_ROOT = package root\n// - built: .../packages/skills/dist/index.js -> HERE = dist -> PKG_ROOT = package root\n// (tsup bundles everything into dist/index.js; `dirname` still yields `dist`.)\nconst HERE = dirname(fileURLToPath(import.meta.url)); // .../src OR .../dist\nconst PKG_ROOT = dirname(HERE); // .../packages/skills\nexport const BUILTIN_DIR = join(PKG_ROOT, 'builtin');\nexport const INTEGRATIONS_DIR = join(PKG_ROOT, 'integrations');\n\n/** Read a `<name>/SKILL.md` + its optional `references/` directory. Shared by\n * builtin + integration discovery (both share the SKILL.md + references shape;\n * the difference is the `integration.json` declaration). */\nfunction readSkillMd(dir: string): Pick<BuiltinSkill, 'skillMd' | 'frontmatter' | 'references'> {\n const skillMd = readFileSync(join(dir, 'SKILL.md'), 'utf8');\n const frontmatter = parseFrontmatter(skillMd);\n const refsDir = join(dir, 'references');\n let references: BuiltinSkill['references'] = [];\n try {\n references = readdirSync(refsDir)\n .filter((f) => f.endsWith('.md'))\n .sort()\n .map((f) => ({ name: f, content: readFileSync(join(refsDir, f), 'utf8') }));\n } catch {\n references = []; // no references/ dir is fine\n }\n return { skillMd, frontmatter, references };\n}\n\nexport function discoverBuiltin(builtinDir: string = BUILTIN_DIR): BuiltinSkill[] {\n const dirs = readdirSync(builtinDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name.startsWith('noir-'))\n .map((e) => e.name)\n .sort();\n return dirs.map((name) => {\n const dir = join(builtinDir, name);\n return { name, dir, ...readSkillMd(dir) };\n });\n}\n\n/**\n * Discover every shipped integration under `integrations/<name>/` (sibling of\n * `builtin/`). Each entry is a builtin-shaped skill (validated `SKILL.md` +\n * optional references) PLUS a parsed + validated `integration.json`\n * declaration. The dir is resolved relative to the package root by default —\n * when `integrationsDir` is overridden (tests), only that dir is scanned.\n *\n * Throws an aggregate error if any integration is malformed (missing\n * `SKILL.md`, bad frontmatter, or an `integration.json` that fails the Zod\n * schema) — fail-fast, atomic-ish, mirroring the builtin compiler's posture.\n */\nexport function discoverIntegrations(\n integrationsDir: string = INTEGRATIONS_DIR,\n): IntegrationSkill[] {\n let entries: string[];\n try {\n entries = readdirSync(integrationsDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && e.name.startsWith('noir-'))\n .map((e) => e.name)\n .sort();\n } catch {\n // No integrations/ dir (or unreadable) = no integrations shipped here.\n return [];\n }\n return entries.map((name) => {\n const dir = join(integrationsDir, name);\n const declarationRaw = JSON.parse(\n readFileSync(join(dir, 'integration.json'), 'utf8'),\n ) as unknown;\n let declaration: IntegrationDeclaration;\n try {\n declaration = parseIntegration(declarationRaw);\n } catch (err) {\n // ZodError → wrap with the integration's dir for an actionable message.\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Invalid integration.json in ${dir}: ${msg}`);\n }\n if (declaration.name !== name) {\n throw new Error(\n `Integration dir \"${name}\" must equal declaration name \"${declaration.name}\"`,\n );\n }\n return { name, dir, ...readSkillMd(dir), declaration, declarationRaw };\n });\n}\n\n/**\n * Discover the full shipped pack — builtins + integrations — in one call. Used\n * by `emitSkillsToDir` so `noir init`/`sync` emit BOTH. The\n * `integrationsDir` defaults to the sibling of `builtinDir` so a test fixture\n * that overrides only `builtinDir` cleanly resolves integrations to a\n * non-existent path (returns `[]`) instead of accidentally picking up the\n * shipped integrations.\n */\nexport function discoverAll(opts: { builtinDir?: string; integrationsDir?: string } = {}): {\n builtins: BuiltinSkill[];\n integrations: IntegrationSkill[];\n} {\n const builtinDir = opts.builtinDir ?? BUILTIN_DIR;\n const integrationsDir = opts.integrationsDir ?? join(dirname(builtinDir), 'integrations');\n return {\n builtins: discoverBuiltin(builtinDir),\n integrations: discoverIntegrations(integrationsDir),\n };\n}\n","// `integration.json` schema. Each integration ships a declaration\n// alongside its `SKILL.md` so the compiler can validate the auth/runtime/SDD\n// contract uniformly and so the host adapter knows whether to widen emission\n// (host MCP config) or stay skill-only.\n//\n// Doctrine: skill-only by default;\n// `gated-write-proxy` for stateless writes routed through a Noir MCP tool\n// `mcp-stdio` for a Noir-spawned stateful runtime (deferred, gated on\n// keychain); `external-mcp` for a config-only pointer at a first-party/community\n// MCP server (emits a host `.mcp.json` server entry, no Noir runtime).\n//\n// Zod v4 (`z.record(keyType, valueType)` is key-first). The schema is additive\n// and defaults to the safest tier — every optional field degrades to \"skill\n// only, manual paste, no host MCP wiring\" — so a minimal `{ \"name\":\n// \"noir-x\" }` declaration still parses + behaves as a pure playbook.\n\nimport * as z from 'zod';\n\n/** Auth shape. Locked: `env-var` only until keychain lands (refuse OAuth,\n * never silently lower the security bar). `fallback:'manual-paste'` keeps the\n * no-token path honest (the playbook tells the user to paste a value); `'none'`\n * is for integrations that genuinely do not need a token (read-only public\n * endpoints). */\nexport const IntegrationAuthSchema = z.object({\n type: z.literal('env-var'),\n tokenEnv: z.string().min(1),\n fallback: z.enum(['manual-paste', 'none']).default('manual-paste'),\n});\n\n/** SDD two-way binding. `intakeFrom` declares the external artifact kind the\n * `noir-brainstorming` skill pulls from (which absorbed noir-intake);\n * `writeBack` enumerates the fields `noir-wrap` pushes back at session end\n * (which absorbed noir-document). Strings (not enums) for `writeBack` so a\n * per-integration vocabulary stays expressible without\n * churning the schema. */\nexport const IntegrationSddSchema = z\n .object({\n intakeFrom: z.enum(['task', 'issue', 'none']).optional(),\n writeBack: z.array(z.string()).default([]),\n })\n .default({ writeBack: [] });\n\n/** Host MCP declaration. Only meaningful when `runtime ∈ {mcp-stdio,\n * external-mcp}`; `null` for `none`/`gated-write-proxy` (ClickUp — writes go\n * through Noir's own MCP tool, never the host's). */\nexport const IntegrationMcpSchema = z\n .object({\n command: z.string().min(1),\n transport: z.enum(['stdio', 'http']),\n args: z.array(z.string()).optional(),\n url: z.string().optional(),\n env: z.record(z.string(), z.string()).optional(),\n })\n .nullable()\n .default(null);\n\n/** Full `integration.json` declaration. `name` enforces the `noir-` prefix\n * shared with builtins so the unified pack reads consistently. */\nexport const IntegrationDeclarationSchema = z.object({\n name: z.string().regex(/^noir-[a-z0-9]+(?:-[a-z0-9]+)*$/, 'must match noir-<kebab>'),\n auth: IntegrationAuthSchema,\n runtime: z.enum(['none', 'gated-write-proxy', 'mcp-stdio', 'external-mcp']),\n sdd: IntegrationSddSchema,\n mcp: IntegrationMcpSchema,\n});\n\nexport type IntegrationDeclaration = z.infer<typeof IntegrationDeclarationSchema>;\n\n/** Coerce + validate an `integration.json` payload. Throws a ZodError on a\n * malformed declaration — the caller (discover) wraps it with the\n * integration's dir for an actionable message. */\nexport function parseIntegration(json: unknown): IntegrationDeclaration {\n return IntegrationDeclarationSchema.parse(json);\n}\n\n/** Non-throwing variant for the hygiene tests + lint helpers. */\nexport function validateIntegration(\n obj: unknown,\n): { ok: true; value: IntegrationDeclaration } | { ok: false; errors: string[] } {\n const parsed = IntegrationDeclarationSchema.safeParse(obj);\n if (parsed.success) return { ok: true, value: parsed.data };\n return { ok: false, errors: parsed.error.issues.map((i: { message: string }) => i.message) };\n}\n\n/** Runtime → host-MCP emission predicate. The compiler widens emission (makes\n * the host MCP block available to the adapter) ONLY for these tiers. The\n * adapter ultimately decides per-host what to render — for Claude, `external-\n * mcp` becomes a `.mcp.json` server entry; `mcp-stdio` registers through the\n * existing `noir mcp serve --stdio` entry so no NEW server entry is emitted. */\nexport function runtimeEmitsHostMcp(runtime: IntegrationDeclaration['runtime']): boolean {\n return runtime === 'mcp-stdio' || runtime === 'external-mcp';\n}\n","// Structural quality checks for the builtin skill pack — the artifact\n// quality gate.\n//\n// Split from compiler.ts so `validateSkill` (hard errors) and `lintSkill` (soft\n// warnings) share one source of truth for the body-structure rules, and so the\n// rules are unit-testable in isolation. Canonical template:\n//\n// Overview → When to use → Procedure → Verification → Notes\n//\n// Rules here implement the researched canon (Anthropic best practices, the\n// agentskills.io spec, Claude Code docs): required sections, a <500-line body\n// budget, one-level-deep references (no chained refs), and a WHAT+WHEN\n// description. `checkRequiredSections` is the load-bearing check — it's what\n// forces every SKILL.md to carry a real playbook shape, not a shell.\n\nimport { ARTIFACT_TYPES } from '@noir-ai/core';\nimport type { BuiltinSkill } from './types.js';\n\n/** The max body length the canon recommends (Anthropic: \"under 500 lines\").\n * SKILL.md is a navigator, not a repository — split to references/ past this. */\nexport const MAX_BODY_LINES = 500;\n\n/** The floor for a full (non-stub) playbook body. Stubs were ~13 lines; a real\n * playbook needs at least this much substance to be loadable as guidance. */\nexport const MIN_FULL_BODY_LINES = 20;\n\n/** The canonical heading a skill MUST carry to describe its trigger conditions.\n * Accepts the canonical template spelling AND common variants so existing skills\n * aren't forced into a single casing. */\nconst WHEN_SECTION = /^## When to use$/im;\n\n/** The canonical heading a skill MUST carry for its numbered workflow. `## Steps`\n * is accepted for backward-compat with noir-wrap; `## Procedure` is canonical. */\nconst PROCEDURE_SECTION = /^## (Procedure|Steps)$/im;\n\n/** One of these closing sections must exist — evidence/notes/degradation. */\nconst CLOSING_SECTION = /^## (Verification|Notes|Fallbacks|Troubleshooting|Why order matters)$/im;\n\n/** The lead cue regex — the FIRST part of the description must be a trigger. */\nconst WHEN_CUE =\n /^(use|using|used|whenever|when|before|after|while|starting|encountering|completing|creating|about to|upon|during|to|for|on)\\b/i;\n\n/** A chained reference link — a reference file that links to ANOTHER reference\n * file (`references/a.md` → `references/b.md` or `../references/b.md`). The\n * canon forbids deep nesting (agentskills.io: \"no chained references\"). */\nconst CHAINED_REF_RE = /\\]\\((\\/?\\.?\\.?\\/)?(references\\/|\\.\\.\\/references\\/)/i;\n\n/**\n * Which required sections are missing from a skill body. Returns a list of\n * human-readable section names, e.g. `['## When to use', '## Verification']`.\n * Empty array = the body carries the canonical playbook shape.\n */\nexport function missingSections(body: string): string[] {\n const missing: string[] = [];\n if (!WHEN_SECTION.test(body)) missing.push('## When to use');\n if (!PROCEDURE_SECTION.test(body)) missing.push('## Procedure (or ## Steps)');\n if (!CLOSING_SECTION.test(body)) {\n missing.push('one of ## Verification / ## Notes / ## Fallbacks / ## Troubleshooting');\n }\n return missing;\n}\n\n/** True when the body is within the canon line budget. */\nexport function withinLineBudget(body: string, max: number = MAX_BODY_LINES): boolean {\n return body.split('\\n').length <= max;\n}\n\n/** Reference files that point at another reference file (chained — forbidden).\n * Returns the offending reference names. */\nexport function chainedReferences(skill: BuiltinSkill): string[] {\n return skill.references.filter((r) => CHAINED_REF_RE.test(r.content)).map((r) => r.name);\n}\n\n/**\n * True when the description carries BOTH a WHEN trigger lead AND a WHAT clause.\n * The rule: `description` MUST lead with a WHEN cue (existing compiler rule)\n * AND contain a compact WHAT clause naming what the skill does.\n *\n * Two canonical shapes both pass:\n * \"Use when turning an idea into a spec — draft the spec.\" (WHEN → WHAT)\n * \"Use when a task writes back to ClickUp: update its status.\" (WHEN → WHAT)\n * The description MUST lead with a cue (the WHEN-only and WHAT-only schools are\n * both rejected). The WHAT clause is the part after the trigger phrase — split\n * on an em/en dash or a period that ends the trigger phrase. A real WHAT clause\n * has ≥3 words naming the action, so a bare \"Use when…\" fails.\n */\nexport function isWhatWhenDescription(description: string): boolean {\n const trimmed = description.trim();\n if (!trimmed) return false;\n if (!WHEN_CUE.test(trimmed)) return false; // must lead with a cue\n // Split off the trigger phrase: after an em/en dash, or after the first period.\n // \"Use when X — draft the spec.\" → whatPart = \"draft the spec.\"\n const whatPart = trimmed\n .split(/[—–]|(?<=\\.) /)\n .slice(1)\n .join(' ')\n .trim();\n return whatPart.split(/\\s+/).filter(Boolean).length >= 3;\n}\n\n/** True when the description leads with a WHEN cue (the existing rule, kept\n * here so validate + lint + hygiene share one implementation). */\nexport function looksLikeWhenDescription(description: string): boolean {\n return WHEN_CUE.test(description.trim());\n}\n\n/**\n * Soft-quality warnings for `lintSkill`. Each returns a short rule id + message.\n * These are advisory — a skill can pass `validateSkill` and still carry lint\n * warnings that the author should resolve. Rules are drawn from the researched\n * anti-pattern list (no examples, thin body, first/second person narration,\n * voodoo constants, time-sensitive version pins).\n */\nexport function lintWarnings(skill: BuiltinSkill): string[] {\n const warnings: string[] = [];\n const body = skill.skillMd; // linting the whole file (frontmatter + body) is fine for prose checks\n const bodyLines = body.split('\\n').length;\n\n if (bodyLines < MIN_FULL_BODY_LINES + 6) {\n // frontmatter is ~6 lines; a full playbook should clear 20 body lines\n warnings.push('thin-body: full playbook body is under 20 lines');\n }\n // Concrete examples: at least one fenced block or an \"example:\" mention.\n const hasExample = /```/.test(body) || /\\bexample:?\\b/i.test(body) || /\\be\\.g\\.\\b/i.test(body);\n if (!hasExample) {\n warnings.push('no-example: no concrete code fence or worked example in the body');\n }\n // First/second-person narration — \"I/we/you\" as the agent doing work.\n if (\n /\\b(I|we|you)\\s+(will|should|can|need|must|do|write|create|implement|run|build)\\b/i.test(body)\n ) {\n warnings.push('first-person: narration addresses the reader instead of imperative steps');\n }\n // Voodoo constants: a bare number with a magnitude claim but no justification\n // (\"wait 5 seconds\" without why). Very loose — catches \"wait N seconds\" without\n // a \"why\".\n if (\n /\\b(?:wait|sleep|retry|backoff|limit|cap)\\s+[a-z]*\\s*(\\d{1,4})\\b/i.test(body) &&\n !/because|to (avoid|prevent|give|let)/i.test(body)\n ) {\n warnings.push('voodoo-constant: numeric threshold without a stated reason');\n }\n // Time-sensitive version pins outside a Legacy section.\n const hasVersionPin = /as of \\d{4}|\\bversion \\d+\\.\\d+\\.\\d+\\b|\"v\\d+\\.\\d+\"/i.test(body);\n const hasLegacySection = /^## Legacy|^## Old patterns/i.test(body);\n if (hasVersionPin && !hasLegacySection) {\n warnings.push('time-sensitive: version/date pin outside a Legacy section');\n }\n return warnings;\n}\n\n/** `.noir/` subdirectories a skill may legitimately reference that are NOT\n * per-type artifact dirs (store db, audit export, rules, daemon state). */\nconst NON_ARTIFACT_DIRS = new Set(['store', 'audit', 'rules', 'state']);\n\n/** Canonical artifact directory → its type code(s) (from the artifact registry).\n * One directory can host multiple kinds (`subagents/` → BR + RP). */\nconst DIR_TO_CODES: ReadonlyMap<string, ReadonlySet<string>> = (() => {\n const m = new Map<string, Set<string>>();\n for (const t of Object.values(ARTIFACT_TYPES)) {\n const codes = m.get(t.dir);\n if (codes) codes.add(t.code);\n else m.set(t.dir, new Set([t.code]));\n }\n return m;\n})();\n\n/**\n * `.noir/…` output-path drift: a skill body or reference that names a `.noir/`\n * directory absent from the artifact registry (e.g. `.noir/sdd/`), or a file\n * under a canonical artifact directory whose name carries none of that\n * directory's type codes (e.g. `.noir/plans/<date>-<slug>.md` instead of\n * `PL-<NNNN>-…`). Hard errors — the host would mint non-standard artifacts.\n * See `docs/reference/artifact-format.md`.\n */\nexport function artifactPathDrift(skill: BuiltinSkill): string[] {\n const texts = [skill.skillMd, ...skill.references.map((r) => r.content)];\n const drifts = new Set<string>();\n // `.noir/<dir>/<name>` — name runs to whitespace/backtick/quote/paren so\n // `<date>-<slug>.md` placeholders are captured whole.\n const re = /\\.noir\\/([a-zA-Z0-9-]+)\\/([^\\s`'\"()]+)/g;\n for (const text of texts) {\n for (const m of text.matchAll(re)) {\n const dir = m[1];\n const name = m[2];\n if (!dir || !name) continue;\n const codes = DIR_TO_CODES.get(dir);\n if (codes === undefined) {\n if (!NON_ARTIFACT_DIRS.has(dir)) {\n drifts.add(\n `.noir/${dir}/ is not a canonical artifact directory (see docs/reference/artifact-format.md)`,\n );\n }\n continue;\n }\n const expected = [...codes].join('/');\n if (![...codes].some((c) => name.startsWith(`${c}-`))) {\n drifts.add(\n `.noir/${dir}/${name} must be ${expected}-<NNNN>-… (see docs/reference/artifact-format.md)`,\n );\n }\n }\n }\n return [...drifts];\n}\n","// Offline behavioral-eval harness for the skill pack.\n//\n// Skills are declared as `evals/**/evals.json` per the agentskills.io format\n// (`{skill_name, evals:[{id, prompt, expected_output, assertions?}]}`). The\n// harness loads every suite and runs OFFLINE assertions only — `contains`,\n// `not-contains`, `regex`, `length-gte` — no LLM, no network, so it runs in\n// `pnpm test` (the project's offline/free constraint). This is the executable\n// that turns the JSON declaration into a vitest suite (see test/evals.test.ts).\n//\n// The evals are deliberately simple: they assert that a skill's PROMPT → a\n// simulated agent answer satisfies structural expectations. They are NOT\n// LLM-judge evals (not implemented yet); they are the CI-safe\n// baseline that catches regressions in the skill's core directives.\n\nimport { readdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/** A single offline assertion on the produced output. */\nexport type EvalAssertion =\n | { type: 'contains'; value: string }\n | { type: 'not-contains'; value: string }\n | { type: 'regex'; value: string }\n | { type: 'length-gte'; value: number };\n\n/** One eval case: a prompt + the expected directive the skill must produce. */\nexport interface SkillEval {\n id: string;\n prompt: string;\n expected_output: string;\n assertions?: EvalAssertion[];\n}\n\n/** A suite = one skill's evals (the `evals.json` shape). */\nexport interface EvalSuite {\n skill_name: string;\n evals: SkillEval[];\n}\n\n/** Validate an assertion's shape (fail-fast on a malformed evals.json). */\nfunction parseAssertion(a: unknown): EvalAssertion {\n if (typeof a !== 'object' || a === null) throw new Error('assertion must be an object');\n const { type, value } = a as { type?: unknown; value?: unknown };\n switch (type) {\n case 'contains':\n case 'not-contains':\n case 'regex':\n if (typeof value !== 'string' || value.length === 0) {\n throw new Error(`assertion '${String(type)}' requires a non-empty string value`);\n }\n return { type, value } as EvalAssertion;\n case 'length-gte':\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new Error(\"assertion 'length-gte' requires a finite number value\");\n }\n return { type, value } as EvalAssertion;\n default:\n throw new Error(`unknown assertion type '${String(type)}'`);\n }\n}\n\n/** Parse + validate a raw `evals.json` payload. Throws on malformed shape. */\nexport function parseEvalSuite(raw: unknown): EvalSuite {\n if (typeof raw !== 'object' || raw === null) throw new Error('evals.json must be an object');\n const { skill_name, evals } = raw as { skill_name?: unknown; evals?: unknown };\n if (typeof skill_name !== 'string' || skill_name.length === 0) {\n throw new Error('evals.json requires a non-empty string `skill_name`');\n }\n if (!Array.isArray(evals) || evals.length === 0) {\n throw new Error(`evals.json for ${skill_name} requires a non-empty 'evals' array`);\n }\n const parsed: SkillEval[] = evals.map((e, i) => {\n if (typeof e !== 'object' || e === null) throw new Error(`eval[${i}] must be an object`);\n const { id, prompt, expected_output, assertions } = e as {\n id?: unknown;\n prompt?: unknown;\n expected_output?: unknown;\n assertions?: unknown;\n };\n if (typeof id !== 'string' || id.length === 0)\n throw new Error(`eval[${i}] requires a string id`);\n if (typeof prompt !== 'string' || prompt.length === 0) {\n throw new Error(`eval ${id} requires a string prompt`);\n }\n if (typeof expected_output !== 'string') {\n throw new Error(`eval ${id} requires a string expected_output`);\n }\n return {\n id,\n prompt,\n expected_output,\n assertions: Array.isArray(assertions) ? assertions.map(parseAssertion) : undefined,\n };\n });\n return { skill_name, evals: parsed };\n}\n\n/** Run a list of assertions against `output`. Returns pass + per-assertion failures. */\nexport function runAssertions(\n output: string,\n assertions: EvalAssertion[],\n): { pass: boolean; failures: string[] } {\n const failures: string[] = [];\n for (const a of assertions) {\n switch (a.type) {\n case 'contains':\n if (!output.includes(a.value)) failures.push(`expected to contain \"${a.value}\"`);\n break;\n case 'not-contains':\n if (output.includes(a.value)) failures.push(`expected NOT to contain \"${a.value}\"`);\n break;\n case 'regex':\n if (!new RegExp(a.value, 'i').test(output)) failures.push(`expected to match /${a.value}/`);\n break;\n case 'length-gte':\n if (output.length < a.value)\n failures.push(`expected length >= ${a.value} (got ${output.length})`);\n break;\n }\n }\n return { pass: failures.length === 0, failures };\n}\n\n// Package root (evals/ is a sibling of src/): resolve the evals dir the same\n// way discover.ts resolves builtin/. Works under vitest (src) + built (dist).\nconst HERE = dirname(fileURLToPath(import.meta.url));\nconst PKG_ROOT = dirname(HERE);\nexport const EVALS_DIR = join(PKG_ROOT, 'evals');\n\n/**\n * Load every eval suite under `dir` (default the shipped `evals/` dir — each\n * subdir carries an `evals.json`). Skips non-`.json` files and missing dirs\n * (no evals = no-op). Fail-fast on a malformed suite (a broken evals.json is a\n * bug, not a skip).\n */\nexport function loadEvalSuites(dir: string = EVALS_DIR): EvalSuite[] {\n let entries: string[];\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .sort();\n } catch {\n return []; // no evals/ dir (or unreadable) = no suites shipped\n }\n const suites: EvalSuite[] = [];\n for (const name of entries) {\n const file = join(dir, name, 'evals.json');\n try {\n const raw = JSON.parse(readFileSync(file, 'utf8')) as unknown;\n suites.push(parseEvalSuite(raw));\n } catch (err) {\n // Missing evals.json in a subdir is fine (it may be a scratch dir); a\n // malformed one is not. Distinguish by existence.\n try {\n readFileSync(file, 'utf8');\n throw err; // exists but malformed → rethrow\n } catch (re) {\n if (re === err) throw err;\n // ENOENT → skip silently.\n }\n }\n }\n return suites;\n}\n\n/** Evaluate one suite: for each eval, check its assertions against\n * `expected_output` (the directive the skill should produce). Returns pass/fail\n * per eval with the assertion failures. This is the offline core the vitest\n * runner drives. */\nexport function evaluateSuite(\n suite: EvalSuite,\n): Array<{ id: string; pass: boolean; failures: string[] }> {\n return suite.evals.map((e) => ({\n id: e.id,\n pass: e.assertions ? runAssertions(e.expected_output, e.assertions).pass : true,\n failures: e.assertions ? runAssertions(e.expected_output, e.assertions).failures : [],\n }));\n}\n","// Runtime-derived skill registry.\n//\n// The registry is NOT a committed file: frontmatter is the single source of\n// truth and `buildRegistry()` derives a queryable index from `discoverAll()`\n// on demand (the CLI calls it; no drift risk, since nothing is persisted). Every\n// field comes from either frontmatter (`metadata.category`, `metadata.version`)\n// or a shape check (`status` from the stub marker, `referenceCount`/`lines`\n// from the discovered files).\n\nimport { discoverAll } from './discover.js';\nimport type { BuiltinSkill } from './types.js';\n\n/** The pack-wide `noir-` namespace a registry entry always belongs to. */\nexport const NOIR_NAMESPACE = 'noir-';\n\n/** One row in the derived registry. */\nexport interface SkillRegistryEntry {\n /** Canonical `noir-<kebab>` id (== dir name). */\n name: string;\n /** `builtin` for the shipped pack; `integration` for an `integrations/<name>/`. */\n kind: 'builtin' | 'integration';\n /** Category from `metadata.category`; falls back to the `noir-`-stripped name\n * (a newly-authored skill still gets a sensible cell). */\n category: string;\n /** Per-skill version from `metadata.version`; defaults to `0.0.0` when absent\n * (the pack-level version in package.json is the authoritative release). */\n version: string;\n /** `full` when the body has no `> **Stub:**` marker; `stub` otherwise. */\n status: 'full' | 'stub';\n /** Lifecycle stage of the skill. `active` = currently shipped and usable;\n * `deprecated` = superseded (a renamed/merged predecessor).\n * The curated pack has no deprecated members today. */\n lifecycle: 'active' | 'deprecated';\n /** The WHAT+WHEN description (the trigger the host sees). */\n description: string;\n /** Number of `references/*.md` files. */\n referenceCount: number;\n /** Total SKILL.md line count (frontmatter + body). */\n lines: number;\n}\n\n/** Derive the category from the skill name when `metadata.category` is absent. */\nfunction fallbackCategory(name: string): string {\n return name.replace(/^noir-/, '') || 'general';\n}\n\n/** Map a discovered builtin/integration to a registry row. */\nfunction toEntry(s: BuiltinSkill, kind: 'builtin' | 'integration'): SkillRegistryEntry {\n const category = s.frontmatter.metadata?.category?.trim() || fallbackCategory(s.name);\n const version = s.frontmatter.metadata?.version?.trim() || '0.0.0';\n const status: 'full' | 'stub' = s.skillMd.includes('> **Stub:**') ? 'stub' : 'full';\n const lines = s.skillMd.split('\\n').length;\n return {\n name: s.name,\n kind,\n category,\n version,\n status,\n // Every shipped skill is active; `deprecated` is reserved for a future\n // superseded member.\n lifecycle: 'active' as const,\n description: typeof s.frontmatter.description === 'string' ? s.frontmatter.description : '',\n referenceCount: s.references.length,\n lines,\n };\n}\n\n/**\n * Build the full registry from `discoverAll()` — builtins + integrations in\n * one list, sorted by name. Pure (no I/O beyond discovery); never throws for\n * a malformed skill (a discovery failure surfaces upstream, the same as the\n * compiler's fail-fast).\n */\nexport function buildRegistry(): SkillRegistryEntry[] {\n const { builtins, integrations } = discoverAll();\n return [\n ...builtins.map((b) => toEntry(b, 'builtin')),\n ...integrations.map((i) => toEntry(i, 'integration')),\n ].sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/** Convenience: registry filtered to a single category (CLI grouping). */\nexport function registryByCategory(category: string): SkillRegistryEntry[] {\n return buildRegistry().filter((e) => e.category === category);\n}\n","// Tokens that must NOT survive a port (predecessor plugin internals + Superpowers\n// rhetoric). Shared by the compiler's lint helper and the hygiene tests.\nexport const FORBIDDEN_RESIDUE: readonly string[] = [\n 'workflow/<task', // predecessor state file\n 'noir-workflow.mode', // predecessor mode flag\n 'noir-workflow', // predecessor plugin name (as a plugin/path reference)\n '@uiigateway', // predecessor Angular specifics\n // NOTE: 'ClickUp'/'clickup' were forbidden during the predecessor-port era (the\n // ClickUp REST precedent lived in the deleted noir-workflow plugin). ClickUp is\n // now reintroduced as a first-class Noir integration (skills/integrations/noir-clickup),\n // so the token is no longer residue — it is the integration's legitimate subject.\n '<EXTREMELY-IMPORTANT', // Superpowers rhetoric\n 'SUBAGENT-STOP', // Superpowers rhetoric\n 'plugins/noir-workflow', // predecessor path\n];\n"],"mappings":"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","names":["dirname","join","join","dirname","readdirSync","readFileSync","dirname","join","fileURLToPath","HERE","PKG_ROOT"]}
@@ -4,13 +4,13 @@ Base URL: `https://api.clickup.com/api/v2`
4
4
  Auth header: `Authorization: pk_<personal-token>` (NO `Bearer` prefix — a Bearer-prefixed header is rejected with `401`).
5
5
  Token env var: `CLICKUP_API_TOKEN` (resolved server-side via the `integrations_auth` MCP tool; manual-paste fallback when absent).
6
6
 
7
- This reference covers the 5 flows noir-clickup implements. It is the canonical source of truth for the skill playbook and the `noir_clickup_write` gated proxy (X-T3).
7
+ This reference covers the 7 flows noir-clickup implements. It is the canonical source of truth for the skill playbook and the `noir_clickup_write` gated proxy.
8
8
 
9
9
  ## Endpoints used
10
10
 
11
11
  ### 1. Get task — `GET /task/{id}`
12
12
 
13
- | | |
13
+ | Field | Value |
14
14
  |---|---|
15
15
  | Method | `GET` |
16
16
  | Path | `/task/{task_id}` or `/task/{custom_task_id}?custom_task_ids=true&team_id={team_id}` |
@@ -56,7 +56,7 @@ Notes:
56
56
 
57
57
  ### 2. Update task — `PUT /task/{task_id}`
58
58
 
59
- | | |
59
+ | Field | Value |
60
60
  |---|---|
61
61
  | Method | `PUT` |
62
62
  | Path | `/task/{task_id}` |
@@ -77,7 +77,7 @@ Response: the updated task object (same shape as GET). Returns `400` if `status`
77
77
 
78
78
  ### 3. Create subtask — `POST /list/{list_id}/task` (+ `PUT /task/{sub}`)
79
79
 
80
- | | |
80
+ | Field | Value |
81
81
  |---|---|
82
82
  | Method | `POST` |
83
83
  | Path | `/list/{list_id}/task` |
@@ -98,7 +98,7 @@ Response: the new task object. Optional follow-up `PUT /task/{new_sub_id} { "sta
98
98
 
99
99
  ### 4. Comment — `POST /task/{task_id}/comment`
100
100
 
101
- | | |
101
+ | Field | Value |
102
102
  |---|---|
103
103
  | Method | `POST` |
104
104
  | Path | `/task/{task_id}/comment` |
@@ -159,6 +159,7 @@ X-RateLimit-Reset: 1700000000
159
159
  - `PUT /task/{id}`
160
160
  - `POST /list/{list_id}/task`
161
161
  - `POST /task/{id}/comment`
162
+ - `POST /task/{id}/attachment`
162
163
 
163
164
  Optional auxiliary reads (skill-side only):
164
165
  - `GET /list/{list_id}` — to read the list's `statuses` array before a status PUT (community-attested; falls back to probe + handle `400`).
@@ -169,7 +170,7 @@ The skill NEVER follows a URL found inside a response field (no chasing `url`, `
169
170
 
170
171
  | Env / config | Source |
171
172
  |---|---|
172
- | `CLICKUP_API_TOKEN` | env var (`pk_...`); resolved by `integrations_auth` MCP tool (X-T3). **Placement:** `.noir/.env` is the recommended home and WINS for the keys it defines; the real environment (a CI secret store, `~/.zshenv`) is the fallback for keys the file leaves unset. Machine-global files (`~/.claude/settings.json` `env`) are that same fallback level and cannot shadow it; a `VAR=value noir …` prefix is not an override either (it is the same level) — a per-invocation value belongs in `run.profiles.<n>.env`. See `docs/how-to/configure-env.md`. |
173
+ | `CLICKUP_API_TOKEN` | env var (`pk_...`); resolved by `integrations_auth` MCP tool. **Placement:** `.noir/.env` is the recommended home and WINS for the keys it defines; the real environment (a CI secret store, `~/.zshenv`) is the fallback for keys the file leaves unset. Machine-global files (`~/.claude/settings.json` `env`) are that same fallback level and cannot shadow it; a `VAR=value noir …` prefix is not an override either (it is the same level) — a per-invocation value belongs in `run.profiles.<n>.env`. See `docs/how-to/configure-env.md`. |
173
174
  | `team_id` | `integrations.clickup.teamId` in `.noir/config.yml` (optional; required only for custom-id reads). |
174
175
  | `list_id` (default) | `integrations.clickup.listId` (optional; flows 3 + 5 require a list id — use the task's `list.id` otherwise). |
175
176
  | `space_id` | `integrations.clickup.spaceId` (optional; informational). |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noir-ai/skills",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "Noir skills — the builtin noir-* skill pack, the integrations pack (ClickUp first), and a copy-and-validate compiler.",
5
5
  "license": "MIT",
6
6
  "author": "agaaaptr",
@@ -48,7 +48,7 @@
48
48
  "dependencies": {
49
49
  "yaml": "^2.5.0",
50
50
  "zod": "^4.2.0",
51
- "@noir-ai/core": "1.14.0"
51
+ "@noir-ai/core": "1.15.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^22.17.0"