@noir-ai/skills 1.14.0 → 1.15.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builtin/noir-brainstorming/SKILL.md +2 -2
- package/builtin/noir-doctor/SKILL.md +6 -5
- package/builtin/noir-executing-plans/SKILL.md +2 -2
- package/builtin/noir-planning/SKILL.md +1 -1
- package/builtin/noir-prd/SKILL.md +7 -10
- package/builtin/noir-remember/SKILL.md +1 -1
- package/builtin/noir-rules/SKILL.md +8 -3
- package/builtin/noir-spec/references/spec-template.md +1 -1
- package/builtin/noir-subagent/references/dispatch-guide.md +2 -2
- package/builtin/noir-verifying/SKILL.md +1 -1
- package/builtin/noir-worktree/SKILL.md +2 -2
- package/builtin/noir-wrap/SKILL.md +1 -1
- package/builtin/noir-writing-skills/SKILL.md +7 -3
- package/dist/index.d.ts +38 -18
- package/dist/index.js +26 -10
- package/dist/index.js.map +1 -1
- package/integrations/noir-clickup/references/clickup-api.md +7 -6
- package/package.json +2 -2
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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. **
|
|
23
|
-
3. **
|
|
24
|
-
4. **
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
-
|
|
41
|
-
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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 (
|
|
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
|
|
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
|
|
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).
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
3
|
+
"version": "1.15.0-beta.1",
|
|
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.
|
|
51
|
+
"@noir-ai/core": "1.15.0-beta.1"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^22.17.0"
|