canary-test-cli 7.0.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/skills/README.md +308 -0
- package/agents/skills/canary:generate.md +49 -0
- package/agents/skills/canary:init.md +37 -0
- package/agents/skills/canary:migrate.md +66 -0
- package/agents/skills/claude-code/canary-add-framework/SKILL.md +248 -0
- package/agents/skills/claude-code/canary-blackhawk/SKILL.md +170 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/cli.mjs +188 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/rules.mjs +120 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/scanner.mjs +244 -0
- package/agents/skills/claude-code/canary-blackhawk/scripts/string-literals.mjs +116 -0
- package/agents/skills/claude-code/canary-cassandra/SKILL.md +180 -0
- package/agents/skills/claude-code/canary-cassandra/scripts/cli.mjs +268 -0
- package/agents/skills/claude-code/canary-cassandra/scripts/engine.mjs +95 -0
- package/agents/skills/claude-code/canary-ci-ready/SKILL.md +161 -0
- package/agents/skills/claude-code/canary-ci-ready/skill.yaml +14 -0
- package/agents/skills/claude-code/canary-company-knowledge/SKILL.md +196 -0
- package/agents/skills/claude-code/canary-critical-areas/SKILL.md +142 -0
- package/agents/skills/claude-code/canary-critical-areas/skill.yaml +16 -0
- package/agents/skills/claude-code/canary-edge-case-discovery/SKILL.md +160 -0
- package/agents/skills/claude-code/canary-edge-case-discovery/skill.yaml +16 -0
- package/agents/skills/claude-code/canary-fail-fast/SKILL.md +75 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/cli.mjs +118 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/digest.mjs +69 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/failures.mjs +60 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/fastfail_check.mjs +43 -0
- package/agents/skills/claude-code/canary-fail-fast/scripts/parse.mjs +149 -0
- package/agents/skills/claude-code/canary-failure-impact/SKILL.md +153 -0
- package/agents/skills/claude-code/canary-failure-impact/skill.yaml +15 -0
- package/agents/skills/claude-code/canary-fleet-health/SKILL.md +196 -0
- package/agents/skills/claude-code/canary-generate-test/SKILL.md +185 -0
- package/agents/skills/claude-code/canary-instrument/SKILL.md +157 -0
- package/agents/skills/claude-code/canary-instrument/scripts/cli.mjs +178 -0
- package/agents/skills/claude-code/canary-instrument/scripts/otel_bootstrap/instrument.mjs +96 -0
- package/agents/skills/claude-code/canary-instrument/scripts/otel_bootstrap/playwright-fixture.ts +44 -0
- package/agents/skills/claude-code/canary-instrument/scripts/run_types.mjs +81 -0
- package/agents/skills/claude-code/canary-instrument/scripts/span_reader.mjs +187 -0
- package/agents/skills/claude-code/canary-katana/SKILL.md +243 -0
- package/agents/skills/claude-code/canary-katana/scripts/alarm.mjs +296 -0
- package/agents/skills/claude-code/canary-katana/scripts/cli.mjs +247 -0
- package/agents/skills/claude-code/canary-katana/scripts/diffscan.mjs +0 -0
- package/agents/skills/claude-code/canary-katana/scripts/ledger.mjs +183 -0
- package/agents/skills/claude-code/canary-pr-guardian/SKILL.md +129 -0
- package/agents/skills/claude-code/canary-pr-guardian/skill.yaml +17 -0
- package/agents/skills/claude-code/canary-promote-test/SKILL.md +228 -0
- package/agents/skills/claude-code/canary-savant/SKILL.md +233 -0
- package/agents/skills/claude-code/canary-savant/scripts/cli.mjs +274 -0
- package/agents/skills/claude-code/canary-savant/scripts/restoration.mjs +274 -0
- package/agents/skills/claude-code/canary-savant/scripts/rules.mjs +168 -0
- package/agents/skills/claude-code/canary-savant/scripts/runner.mjs +572 -0
- package/agents/skills/claude-code/canary-savant/scripts/scanner.mjs +374 -0
- package/agents/skills/claude-code/canary-savant/scripts/string-literals.mjs +116 -0
- package/agents/skills/claude-code/canary-setup-harness/SKILL.md +263 -0
- package/agents/skills/claude-code/canary-shadow/SKILL.md +131 -0
- package/agents/skills/claude-code/canary-shadow/scripts/cases.example.json +32 -0
- package/agents/skills/claude-code/canary-shadow/scripts/cli.mjs +195 -0
- package/agents/skills/claude-code/canary-ship/SKILL.md +177 -0
- package/agents/skills/claude-code/canary-ship/skill.yaml +16 -0
- package/agents/skills/claude-code/canary-strix/SKILL.md +130 -0
- package/agents/skills/claude-code/canary-strix/scripts/cli.mjs +255 -0
- package/agents/skills/claude-code/canary-strix/scripts/scanner.mjs +252 -0
- package/agents/skills/claude-code/canary-strix/scripts/terms.mjs +132 -0
- package/agents/skills/claude-code/canary-test-pipeline/SKILL.md +159 -0
- package/agents/skills/claude-code/canary-test-pipeline/skill.yaml +19 -0
- package/agents/skills/claude-code/canary-test-reporter/SKILL.md +138 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/cli.mjs +98 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/json_report.mjs +58 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/parse.mjs +216 -0
- package/agents/skills/claude-code/canary-test-reporter/scripts/render.mjs +114 -0
- package/agents/skills/lib/parse-args.mjs +214 -0
- package/dist/engine/analysis/cli.js +116 -54
- package/dist/engine/analysis/engine.js +34 -16
- package/dist/engine/analysis/reports.js +5 -4
- package/dist/engine/cli-commands.js +252 -106
- package/dist/engine/cli-common.js +15 -24
- package/dist/engine/cli.core.js +37 -11
- package/dist/engine/cli.js +2 -2
- package/dist/engine/company-knowledge-cli.js +2 -2
- package/dist/engine/core/adoption.js +408 -0
- package/dist/engine/core/framework-probes.js +7 -7
- package/dist/engine/core/fs-glob.js +2 -2
- package/dist/engine/core/gate-result.js +17 -0
- package/dist/engine/core/migrator.js +9 -17
- package/dist/engine/core/pattern-matcher.js +23 -5
- package/dist/engine/core/persona.js +421 -0
- package/dist/engine/core/promotion-verdict.js +261 -0
- package/dist/engine/core/quality-scorer.js +15 -2
- package/dist/engine/core/reporter.js +1 -9
- package/dist/engine/core/skill-dispatch.js +115 -0
- package/dist/engine/core/skill-examples.js +392 -0
- package/dist/engine/core/skill-registry.js +59 -4
- package/dist/engine/core/skill-surfaces.js +307 -0
- package/dist/engine/core/static-linter.js +310 -38
- package/dist/engine/core/test-files.js +77 -0
- package/dist/engine/core/ticket-updater.js +1 -7
- package/dist/engine/core/vacuity-scanner.js +726 -0
- package/dist/engine/core/workflow-discovery.js +2 -8
- package/dist/engine/core/workspace-detect.js +7 -6
- package/dist/engine/data/personas/registry.json +36 -0
- package/dist/engine/guardian/adjudication.js +5 -5
- package/dist/engine/guardian/analysis-emit.js +19 -28
- package/dist/engine/guardian/cli.js +150 -51
- package/dist/engine/guardian/coverage.js +1 -1
- package/dist/engine/guardian/diff-coverage/heuristic-tier.js +1 -1
- package/dist/engine/guardian/diff-coverage/orchestrator.js +2 -2
- package/dist/engine/guardian/pr-check.js +322 -33
- package/dist/engine/guardian/pr-comment.js +4 -3
- package/dist/engine/history/cli.js +210 -6
- package/dist/engine/history/ndjson-store.js +9 -5
- package/dist/engine/history/record.js +34 -5
- package/dist/engine/history/run-recorder.js +165 -0
- package/dist/engine/history/schema.js +25 -7
- package/dist/engine/history/store.js +9 -0
- package/dist/engine/mcp-server.js +35 -13
- package/dist/engine/skills-cli.js +249 -23
- package/dist/engine/util/ensure-ascii.js +37 -0
- package/dist/engine/workflow-cli.js +6 -6
- package/dist/gate-result.d.ts +11 -0
- package/dist/gate-result.js +18 -0
- package/dist/reporters/testtracker.d.ts +1 -1
- package/dist/reporters/testtracker.js +1 -1
- package/dist/uninstall.js +12 -5
- package/package.json +3 -2
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-surface skill-declaration inventory and integrity check (#452).
|
|
3
|
+
*
|
|
4
|
+
* A canary skill is not declared in one place. The same name appears in the
|
|
5
|
+
* `SKILL.md` under `agents/skills/`, in the plugin slash command under
|
|
6
|
+
* `commands/`, in the agent definition under `agents/`, and again in the
|
|
7
|
+
* per-host `agents/commands/<host>/` and `agents/agents/<host>/` trees. Each of
|
|
8
|
+
* those files is internally consistent, so every per-file assertion passes even
|
|
9
|
+
* when they disagree with each other or point at something that no longer
|
|
10
|
+
* exists.
|
|
11
|
+
*
|
|
12
|
+
* That is the bug class #452 describes: grading a value against a hardcoded
|
|
13
|
+
* literal is blind to a fact that is right on one surface and stale on another.
|
|
14
|
+
* The remedy is to grade against **agreement** and **resolvability** instead:
|
|
15
|
+
* enumerate the surfaces that declare a name, then verify the declaration
|
|
16
|
+
* against reality.
|
|
17
|
+
*
|
|
18
|
+
* ## The two rules it enforces
|
|
19
|
+
*
|
|
20
|
+
* 1. **A declared name is the name the invocation path uses.** Skill discovery
|
|
21
|
+
* ({@link ../core/skill-registry.js}) keys off the frontmatter `name:`, while
|
|
22
|
+
* every doc invokes the skill by its directory. When those diverge the
|
|
23
|
+
* documented command misses and nothing says so.
|
|
24
|
+
* 2. **A documented invocation reaches something that exists and can run.** A
|
|
25
|
+
* `cli:` path that is absent, or present at mode 0644, installs cleanly,
|
|
26
|
+
* lists cleanly, documents cleanly — and cannot run (#478). A
|
|
27
|
+
* `canary skills run <name>` in prose whose target has been renamed away is
|
|
28
|
+
* the same defect one layer up.
|
|
29
|
+
*
|
|
30
|
+
* ## What it deliberately refuses to do
|
|
31
|
+
*
|
|
32
|
+
* It does not adjudicate prose. When two surfaces carry different descriptions
|
|
33
|
+
* there is no principled culprit: #452's triage settled that majority-wins is
|
|
34
|
+
* wrong at N=2 and wrong in general when one surface is the write path and the
|
|
35
|
+
* others are read models, and that only a fixture-intent floor may name a
|
|
36
|
+
* culprit. No such floor exists for skill prose, so divergent descriptions are
|
|
37
|
+
* not reported at all rather than reported against an invented winner. Adding
|
|
38
|
+
* that arbitration is the remaining half of #452 and is out of scope here.
|
|
39
|
+
*
|
|
40
|
+
* ## Denominator
|
|
41
|
+
*
|
|
42
|
+
* {@link checkSurfaces} returns a {@link GateResult} whose `checked` is the
|
|
43
|
+
* number of surface declarations actually inspected. Zero means the layout was
|
|
44
|
+
* renamed underneath the check, and `gateOutcome` turns that into a loud
|
|
45
|
+
* abstention rather than "all surfaces agree" (#508).
|
|
46
|
+
*/
|
|
47
|
+
import { accessSync, constants, readFileSync, readdirSync } from 'node:fs';
|
|
48
|
+
import { basename, extname, join } from 'node:path';
|
|
49
|
+
import { SkillRegistry } from './skill-registry.js';
|
|
50
|
+
/** Where a skill name was declared. */
|
|
51
|
+
export var SurfaceKind;
|
|
52
|
+
(function (SurfaceKind) {
|
|
53
|
+
/** `agents/skills/<host>/<dir>/SKILL.md` — the skill itself. */
|
|
54
|
+
SurfaceKind["Skill"] = "skill";
|
|
55
|
+
/** `agents/skills/*.md` — a flat slash-command skill. */
|
|
56
|
+
SurfaceKind["FlatSkill"] = "flat-skill";
|
|
57
|
+
/** `commands/*.md` — the plugin slash command fronting a skill. */
|
|
58
|
+
SurfaceKind["PluginCommand"] = "plugin-command";
|
|
59
|
+
/** `agents/*.md` — the plugin agent definition. */
|
|
60
|
+
SurfaceKind["PluginAgent"] = "plugin-agent";
|
|
61
|
+
/** `agents/commands/<host>/**` — the per-host command definition. */
|
|
62
|
+
SurfaceKind["HarnessCommand"] = "harness-command";
|
|
63
|
+
/** `agents/agents/<host>/**` — the per-host agent definition. */
|
|
64
|
+
SurfaceKind["HarnessAgent"] = "harness-agent";
|
|
65
|
+
})(SurfaceKind || (SurfaceKind = {}));
|
|
66
|
+
/** What a cross-surface check found. */
|
|
67
|
+
export var SurfaceFindingKind;
|
|
68
|
+
(function (SurfaceFindingKind) {
|
|
69
|
+
/** Frontmatter `name:` disagrees with the directory docs invoke it by. */
|
|
70
|
+
SurfaceFindingKind["NameMismatch"] = "name-mismatch";
|
|
71
|
+
/** A declared `cli:` target does not exist. */
|
|
72
|
+
SurfaceFindingKind["CliMissing"] = "cli-missing";
|
|
73
|
+
/** A declared `cli:` target exists but is not executable (#478). */
|
|
74
|
+
SurfaceFindingKind["CliNotExecutable"] = "cli-not-executable";
|
|
75
|
+
/** A documented `canary skills run <name>` names no discoverable skill. */
|
|
76
|
+
SurfaceFindingKind["UnreachableReference"] = "unreachable-reference";
|
|
77
|
+
})(SurfaceFindingKind || (SurfaceFindingKind = {}));
|
|
78
|
+
/** Vendored trees are not first-party skill surfaces. */
|
|
79
|
+
const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', 'coverage']);
|
|
80
|
+
/**
|
|
81
|
+
* A literal canary skill name. Anchored on the `canary-`/`canary:` prefix every
|
|
82
|
+
* first-party skill carries, which keeps a hypothetical `canary skills run
|
|
83
|
+
* my-skill` in prose from being reported as a dead reference.
|
|
84
|
+
*/
|
|
85
|
+
const SKILL_NAME = /^canary[-:][a-z0-9:_-]*$/;
|
|
86
|
+
/** Every `canary skills run <name>` in the document, placeholders excluded. */
|
|
87
|
+
function referencedSkills(text) {
|
|
88
|
+
const found = new Set();
|
|
89
|
+
const pattern = /canary\s+skills\s+run\s+(\S+)/g;
|
|
90
|
+
for (const match of text.matchAll(pattern)) {
|
|
91
|
+
const token = match[1].replace(/[`'"]/g, '');
|
|
92
|
+
if (SKILL_NAME.test(token))
|
|
93
|
+
found.add(token);
|
|
94
|
+
}
|
|
95
|
+
return [...found].sort();
|
|
96
|
+
}
|
|
97
|
+
/** Read a file, returning null when it cannot be read. */
|
|
98
|
+
function readOrNull(path) {
|
|
99
|
+
try {
|
|
100
|
+
return readFileSync(path, 'utf-8');
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function listDirs(path) {
|
|
107
|
+
try {
|
|
108
|
+
return readdirSync(path, { withFileTypes: true })
|
|
109
|
+
.filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name))
|
|
110
|
+
.map((e) => e.name)
|
|
111
|
+
.sort();
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function listMarkdown(path) {
|
|
118
|
+
try {
|
|
119
|
+
return readdirSync(path, { withFileTypes: true })
|
|
120
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md') && e.name !== 'README.md')
|
|
121
|
+
.map((e) => e.name)
|
|
122
|
+
.sort();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Parse one declaring file into a {@link SurfaceDeclaration}. */
|
|
129
|
+
function declare(kind, path, fallbackName, dirName) {
|
|
130
|
+
const text = readOrNull(path);
|
|
131
|
+
if (text === null)
|
|
132
|
+
return null;
|
|
133
|
+
// The SAME frontmatter parser discovery uses, deliberately: a checker with
|
|
134
|
+
// its own YAML subset would disagree with the runtime it is auditing, and
|
|
135
|
+
// that disagreement is the bug class rather than a detail (#501).
|
|
136
|
+
const fm = SkillRegistry.parseFrontmatter(text);
|
|
137
|
+
const name = typeof fm['name'] === 'string' && fm['name'] ? fm['name'] : fallbackName;
|
|
138
|
+
const description = typeof fm['description'] === 'string' ? fm['description'] : '';
|
|
139
|
+
const cli = typeof fm['cli'] === 'string' && fm['cli'] ? fm['cli'] : null;
|
|
140
|
+
return {
|
|
141
|
+
kind,
|
|
142
|
+
name,
|
|
143
|
+
dirName,
|
|
144
|
+
path,
|
|
145
|
+
description,
|
|
146
|
+
cli,
|
|
147
|
+
references: referencedSkills(text),
|
|
148
|
+
text,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** Nested `agents/skills/<host>/<dir>/SKILL.md` declarations. */
|
|
152
|
+
function nestedSkills(root) {
|
|
153
|
+
const out = [];
|
|
154
|
+
const skillsRoot = join(root, 'agents', 'skills');
|
|
155
|
+
for (const host of listDirs(skillsRoot)) {
|
|
156
|
+
for (const dir of listDirs(join(skillsRoot, host))) {
|
|
157
|
+
const path = join(skillsRoot, host, dir, 'SKILL.md');
|
|
158
|
+
const decl = declare(SurfaceKind.Skill, path, dir, dir);
|
|
159
|
+
if (decl)
|
|
160
|
+
out.push(decl);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
/** Flat `*.md` surfaces directly inside `dir`. */
|
|
166
|
+
function flatSurfaces(dir, kind) {
|
|
167
|
+
const out = [];
|
|
168
|
+
for (const file of listMarkdown(dir)) {
|
|
169
|
+
const path = join(dir, file);
|
|
170
|
+
const decl = declare(kind, path, basename(file, extname(file)), null);
|
|
171
|
+
if (decl)
|
|
172
|
+
out.push(decl);
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
/** Per-host `agents/<commands|agents>/<host>/**\/*.md` surfaces. */
|
|
177
|
+
function hostSurfaces(root, segment, kind) {
|
|
178
|
+
const out = [];
|
|
179
|
+
const base = join(root, 'agents', segment);
|
|
180
|
+
for (const host of listDirs(base)) {
|
|
181
|
+
const hostDir = join(base, host);
|
|
182
|
+
out.push(...flatSurfaces(hostDir, kind));
|
|
183
|
+
// Host trees nest one more level (`harness/harness/<name>.md`).
|
|
184
|
+
for (const sub of listDirs(hostDir)) {
|
|
185
|
+
out.push(...flatSurfaces(join(hostDir, sub), kind));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Enumerate every surface that declares a skill name, in a stable order.
|
|
192
|
+
*
|
|
193
|
+
* Purely a read: no network, no subprocess, no writes. `root` is the repository
|
|
194
|
+
* root, injectable so tests build small fixture trees instead of asserting
|
|
195
|
+
* against the live checkout.
|
|
196
|
+
*/
|
|
197
|
+
export function collectSurfaces(root) {
|
|
198
|
+
return [
|
|
199
|
+
...nestedSkills(root),
|
|
200
|
+
...flatSurfaces(join(root, 'agents', 'skills'), SurfaceKind.FlatSkill),
|
|
201
|
+
...flatSurfaces(join(root, 'commands'), SurfaceKind.PluginCommand),
|
|
202
|
+
...flatSurfaces(join(root, 'agents'), SurfaceKind.PluginAgent),
|
|
203
|
+
...hostSurfaces(root, 'commands', SurfaceKind.HarnessCommand),
|
|
204
|
+
...hostSurfaces(root, 'agents', SurfaceKind.HarnessAgent),
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
/** Process-backed defaults. */
|
|
208
|
+
function defaultSurfaceDeps() {
|
|
209
|
+
return {
|
|
210
|
+
isExecutable: (path) => {
|
|
211
|
+
try {
|
|
212
|
+
// X_OK is exactly what `canary skills run` needs; R_OK alone is #478.
|
|
213
|
+
accessSync(path, constants.X_OK);
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
exists: (path) => {
|
|
221
|
+
try {
|
|
222
|
+
accessSync(path, constants.F_OK);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/** Findings for one declaration's `cli:` target. */
|
|
232
|
+
function cliFindings(decl, deps) {
|
|
233
|
+
if (decl.cli === null)
|
|
234
|
+
return [];
|
|
235
|
+
const target = join(decl.path, '..', decl.cli);
|
|
236
|
+
if (!deps.exists(target)) {
|
|
237
|
+
return [
|
|
238
|
+
{
|
|
239
|
+
kind: SurfaceFindingKind.CliMissing,
|
|
240
|
+
surface: decl.kind,
|
|
241
|
+
name: decl.name,
|
|
242
|
+
path: decl.path,
|
|
243
|
+
detail: `declares \`cli: ${decl.cli}\` but the target does not exist, so ` +
|
|
244
|
+
'`canary skills run` cannot invoke it',
|
|
245
|
+
},
|
|
246
|
+
];
|
|
247
|
+
}
|
|
248
|
+
if (!deps.isExecutable(target)) {
|
|
249
|
+
return [
|
|
250
|
+
{
|
|
251
|
+
kind: SurfaceFindingKind.CliNotExecutable,
|
|
252
|
+
surface: decl.kind,
|
|
253
|
+
name: decl.name,
|
|
254
|
+
path: decl.path,
|
|
255
|
+
detail: `declares \`cli: ${decl.cli}\` but the target is not executable ` +
|
|
256
|
+
'(#478: mode 0644 installs, lists, and documents cleanly, then fails ' +
|
|
257
|
+
'with EACCES mapped to a bare exit 1)',
|
|
258
|
+
},
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Verify every declaration against reality.
|
|
265
|
+
*
|
|
266
|
+
* Classified **advisory**: it is landing on a repository whose surfaces have
|
|
267
|
+
* never been checked before, so its precision is not yet known and promoting it
|
|
268
|
+
* to a blocking gate would teach people to ignore it (ADR 0010). The
|
|
269
|
+
* zero-denominator abstention is not advisory — that path is loud either way.
|
|
270
|
+
*/
|
|
271
|
+
export function checkSurfaces(root, deps = defaultSurfaceDeps()) {
|
|
272
|
+
const surfaces = collectSurfaces(root);
|
|
273
|
+
const known = new Set(surfaces
|
|
274
|
+
.filter((s) => s.kind === SurfaceKind.Skill || s.kind === SurfaceKind.FlatSkill)
|
|
275
|
+
.flatMap((s) => (s.dirName ? [s.name, s.dirName] : [s.name])));
|
|
276
|
+
const findings = [];
|
|
277
|
+
for (const decl of surfaces) {
|
|
278
|
+
if (decl.kind === SurfaceKind.Skill &&
|
|
279
|
+
decl.dirName !== null &&
|
|
280
|
+
decl.name !== decl.dirName) {
|
|
281
|
+
findings.push({
|
|
282
|
+
kind: SurfaceFindingKind.NameMismatch,
|
|
283
|
+
surface: decl.kind,
|
|
284
|
+
name: decl.name,
|
|
285
|
+
path: decl.path,
|
|
286
|
+
detail: `frontmatter declares \`name: ${decl.name}\` but the directory is ` +
|
|
287
|
+
`\`${decl.dirName}\`; discovery keys off the frontmatter, docs invoke ` +
|
|
288
|
+
'the directory, so one of the two paths misses',
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
findings.push(...cliFindings(decl, deps));
|
|
292
|
+
for (const ref of decl.references) {
|
|
293
|
+
if (!known.has(ref)) {
|
|
294
|
+
findings.push({
|
|
295
|
+
kind: SurfaceFindingKind.UnreachableReference,
|
|
296
|
+
surface: decl.kind,
|
|
297
|
+
name: decl.name,
|
|
298
|
+
path: decl.path,
|
|
299
|
+
detail: `documents \`canary skills run ${ref}\`, and no discoverable skill ` +
|
|
300
|
+
'answers to that name',
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return { checked: surfaces.length, findings };
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=skill-surfaces.js.map
|