@adia-ai/adia-ui-forge 0.1.3 → 0.8.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/.claude-plugin/plugin.json +3 -6
- package/CHANGELOG.md +27 -34
- package/README.md +35 -40
- package/agents/a2ui-engineer.md +22 -53
- package/agents/component-author.md +29 -0
- package/agents/framework-verifier.md +26 -0
- package/agents/release-engineer.md +21 -81
- package/agents/routing-corpus.json +293 -64
- package/bin/demo-postwrite-pattern-gate +94 -0
- package/bin/forge-lint +17 -2
- package/bin/sidecar-prewrite-guard +104 -0
- package/commands/deploy.md +9 -0
- package/commands/dogfood.md +10 -0
- package/commands/gen-review.md +9 -0
- package/commands/release.md +10 -0
- package/hooks/hooks.json +15 -0
- package/package.json +3 -6
- package/references/contracts/a2ui-mcp-surface.md +35 -0
- package/references/contracts/migration-guide-format.md +34 -0
- package/references/shared/content-trust.md +19 -74
- package/skills/adia-a2ui/SKILL.md +99 -0
- package/skills/{adia-ui-a2ui → adia-a2ui}/evals/routing-corpus.json +10 -10
- package/skills/adia-a2ui/references/anti-patterns.md +27 -0
- package/skills/adia-a2ui/references/chunk-authoring.md +71 -0
- package/skills/adia-a2ui/references/corpus-discipline.md +68 -0
- package/skills/adia-a2ui/references/eval-diagnostics.md +86 -0
- package/skills/adia-a2ui/references/format-extension-decisions.md +66 -0
- package/skills/adia-a2ui/references/leverage-rules.md +52 -0
- package/skills/adia-a2ui/references/mcp-pipeline-ops.md +83 -0
- package/skills/adia-a2ui/references/mcp-tool-reference.md +59 -0
- package/skills/adia-a2ui/references/pipeline-overview.md +115 -0
- package/skills/adia-a2ui/references/semantic-fail-lifting.md +74 -0
- package/skills/adia-a2ui/references/strategy-engines.md +109 -0
- package/skills/adia-a2ui/references/zettel-calibration.md +104 -0
- package/skills/adia-author/SKILL.md +112 -0
- package/skills/adia-author/evals/routing-corpus.json +222 -0
- package/skills/{adia-ui-authoring → adia-author}/references/anti-patterns.md +33 -4
- package/skills/{adia-ui-authoring → adia-author}/references/authoring-cycle.md +9 -11
- package/skills/adia-author/references/canonical-pattern-index.md +243 -0
- package/skills/{adia-ui-authoring → adia-author}/references/code-style.md +19 -21
- package/skills/adia-author/references/common-gotchas.md +129 -0
- package/skills/adia-author/references/composite-demo-protocol.md +271 -0
- package/skills/{adia-ui-authoring → adia-author}/references/css-patterns.md +19 -6
- package/skills/{adia-ui-authoring → adia-author}/references/lifecycle-patterns.md +1 -0
- package/skills/{adia-ui-authoring → adia-author}/references/llm-bridge.md +3 -5
- package/skills/{adia-ui-authoring → adia-author}/references/module-promotion.md +20 -38
- package/skills/{adia-ui-authoring → adia-author}/references/primitive-audit.md +2 -4
- package/skills/{adia-ui-authoring → adia-author}/references/shell-patterns.md +12 -18
- package/skills/{adia-ui-authoring → adia-author}/references/token-contract.md +5 -7
- package/skills/{adia-ui-authoring → adia-author}/references/worked-example.md +3 -3
- package/skills/{adia-ui-authoring → adia-author}/references/yaml-contract.md +3 -3
- package/skills/{adia-ui-authoring → adia-author}/scripts/build-canonical-pattern-index.mjs +3 -3
- package/skills/adia-deploy/SKILL.md +128 -0
- package/skills/{adia-ui-ops → adia-deploy}/references/deploy-playbooks.md +47 -30
- package/skills/adia-dogfood/SKILL.md +124 -0
- package/skills/adia-dogfood/references/admin-shell-anatomy.md +77 -0
- package/skills/adia-dogfood/references/app-shell-pitfalls.md +66 -0
- package/skills/adia-dogfood/references/card-anatomy-sweep.md +66 -0
- package/skills/adia-dogfood/references/html-attr-sweep.md +63 -0
- package/skills/adia-dogfood/references/native-leak-annotations.md +73 -0
- package/skills/adia-dogfood/references/visual-probe-triage.md +80 -0
- package/skills/{adia-ui-dogfood → adia-dogfood}/scripts/analyze.mjs +30 -14
- package/skills/adia-gen-review/SKILL.md +127 -0
- package/skills/adia-gen-review/references/corpus-html-patterns.md +279 -0
- package/skills/adia-gen-review/references/loop-protocol.md +216 -0
- package/skills/adia-gen-review/references/rubric-cosmetic.md +100 -0
- package/skills/adia-gen-review/references/rubric-decompose.md +101 -0
- package/skills/adia-gen-review/references/rubric-score.md +214 -0
- package/skills/{adia-ui-gen-review → adia-gen-review}/references/scores.schema.json +2 -2
- package/skills/{adia-ui-gen-review → adia-gen-review}/scripts/gen-review-coverage-audit.mjs +4 -4
- package/skills/{adia-ui-gen-review → adia-gen-review}/scripts/gen-review-decompose.mjs +10 -6
- package/skills/{adia-ui-gen-review → adia-gen-review}/scripts/gen-review-status.mjs +5 -5
- package/skills/{adia-ui-gen-review → adia-gen-review}/scripts/validate-cycle-scores.mjs +5 -5
- package/skills/adia-llm-internals/SKILL.md +77 -0
- package/skills/{adia-ui-llm → adia-llm-internals}/references/adapter-contract.md +4 -2
- package/skills/{adia-ui-llm → adia-llm-internals}/references/add-a-provider.md +4 -5
- package/skills/{adia-ui-llm → adia-llm-internals}/references/bridge-facade.md +10 -3
- package/skills/{adia-ui-llm → adia-llm-internals}/references/browser-proxy-boundary.md +1 -1
- package/skills/{adia-ui-llm → adia-llm-internals}/references/model-registry.md +5 -3
- package/skills/{adia-ui-llm → adia-llm-internals}/references/streaming-sse.md +2 -2
- package/skills/adia-release/SKILL.md +72 -0
- package/skills/adia-release/references/changelog-discipline.md +119 -0
- package/skills/adia-release/references/cut-procedure.md +247 -0
- package/skills/adia-release/references/gates-catalog.md +222 -0
- package/skills/adia-release/references/independent-package-release.md +52 -0
- package/skills/adia-release/references/migration-guide-authoring.md +86 -0
- package/skills/adia-release/references/notes-authoring.md +90 -0
- package/skills/adia-release/references/recovery-paths.md +106 -0
- package/skills/{adia-ui-release → adia-release}/scripts/bump.mjs +27 -2
- package/skills/{adia-ui-release → adia-release}/scripts/dispatch-publish.mjs +4 -1
- package/skills/{adia-ui-release → adia-release}/scripts/insert-stub.mjs +1 -1
- package/skills/{adia-ui-release → adia-release}/scripts/package-paths.mjs +6 -0
- package/skills/{adia-ui-release → adia-release}/scripts/promote-unreleased.mjs +1 -1
- package/skills/{adia-ui-release → adia-release}/scripts/release-pack.mjs +13 -21
- package/skills/{adia-ui-release → adia-release}/scripts/tag-lockstep.mjs +5 -1
- package/skills/adia-site-docs/SKILL.md +68 -0
- package/skills/adia-site-docs/evals/audit-report.md +30 -0
- package/skills/adia-site-docs/evals/routing-corpus.json +176 -0
- package/skills/adia-site-docs/intent.md +72 -0
- package/agents/README.md +0 -209
- package/agents/author.md +0 -56
- package/agents/repo-steward.md +0 -56
- package/agents/spec-architect.md +0 -53
- package/agents/tech-lead.md +0 -57
- package/agents/verifier.md +0 -55
- package/bin/lib/audit-axes.mjs +0 -555
- package/bin/lib/dry-run-irreversible.mjs +0 -236
- package/bin/lib/run-skill-evals.mjs +0 -487
- package/bin/lib/teach-router.mjs +0 -250
- package/commands/adia-forge-a2ui.md +0 -10
- package/commands/adia-forge-author.md +0 -10
- package/commands/adia-forge-dogfood.md +0 -8
- package/commands/adia-forge-llm.md +0 -8
- package/commands/adia-forge-orient.md +0 -10
- package/commands/adia-forge-release.md +0 -8
- package/commands/adia-forge-review.md +0 -10
- package/references/shared/pev-rationale.md +0 -64
- package/references/shared/skill-conventions.md +0 -133
- package/skills/adia-ui-a2ui/CHANGELOG.md +0 -32
- package/skills/adia-ui-a2ui/SKILL.md +0 -243
- package/skills/adia-ui-a2ui/evals/adversarial-corpus.json +0 -75
- package/skills/adia-ui-a2ui/evals/teach-routing-cases.json +0 -100
- package/skills/adia-ui-a2ui/references/anti-patterns.md +0 -24
- package/skills/adia-ui-a2ui/references/chunk-authoring.md +0 -88
- package/skills/adia-ui-a2ui/references/corpus-discipline.md +0 -56
- package/skills/adia-ui-a2ui/references/eval-diagnostics.md +0 -127
- package/skills/adia-ui-a2ui/references/fragment-graph.md +0 -91
- package/skills/adia-ui-a2ui/references/mcp-pipeline-ops.md +0 -106
- package/skills/adia-ui-a2ui/references/mcp-tool-reference.md +0 -398
- package/skills/adia-ui-a2ui/references/pipeline-overview.md +0 -175
- package/skills/adia-ui-a2ui/references/semantic-fail-lifting.md +0 -120
- package/skills/adia-ui-a2ui/references/strategy-engines.md +0 -111
- package/skills/adia-ui-a2ui/references/teach-protocol.md +0 -220
- package/skills/adia-ui-a2ui/references/zettel-calibration.md +0 -93
- package/skills/adia-ui-a2ui/scripts/audit-a2ui-roster.mjs +0 -96
- package/skills/adia-ui-a2ui/scripts/teach-route.mjs +0 -157
- package/skills/adia-ui-a2ui/skill.json +0 -38
- package/skills/adia-ui-authoring/CHANGELOG.md +0 -32
- package/skills/adia-ui-authoring/SKILL.md +0 -256
- package/skills/adia-ui-authoring/assets/case-studies/admin-shell-decomposition.md +0 -101
- package/skills/adia-ui-authoring/assets/case-studies/maxtokens-32768-discovery.md +0 -109
- package/skills/adia-ui-authoring/assets/case-studies/theme-panel-promotion.md +0 -113
- package/skills/adia-ui-authoring/evals/adversarial-design-plan-gates.json +0 -138
- package/skills/adia-ui-authoring/evals/routing-corpus.json +0 -245
- package/skills/adia-ui-authoring/references/canonical-pattern-index.md +0 -179
- package/skills/adia-ui-authoring/references/common-gotchas.md +0 -93
- package/skills/adia-ui-authoring/references/composite-demo-protocol.md +0 -1084
- package/skills/adia-ui-authoring/references/teach-protocol.md +0 -428
- package/skills/adia-ui-authoring/scripts/audit-authoring-roster.mjs +0 -148
- package/skills/adia-ui-authoring/skill.json +0 -45
- package/skills/adia-ui-dogfood/CHANGELOG.md +0 -17
- package/skills/adia-ui-dogfood/README.md +0 -62
- package/skills/adia-ui-dogfood/SKILL.md +0 -866
- package/skills/adia-ui-dogfood/skill.json +0 -40
- package/skills/adia-ui-forge/SKILL.md +0 -88
- package/skills/adia-ui-forge/evals/routing-corpus.json +0 -30
- package/skills/adia-ui-gen-review/CHANGELOG.md +0 -108
- package/skills/adia-ui-gen-review/SKILL.md +0 -266
- package/skills/adia-ui-gen-review/references/loop-protocol.md +0 -712
- package/skills/adia-ui-gen-review/references/rubric-cosmetic.md +0 -144
- package/skills/adia-ui-gen-review/references/rubric-decompose.md +0 -117
- package/skills/adia-ui-gen-review/references/rubric-score.md +0 -249
- package/skills/adia-ui-gen-review/references/teach-protocol.md +0 -214
- package/skills/adia-ui-gen-review/skill.json +0 -23
- package/skills/adia-ui-llm/CHANGELOG.md +0 -25
- package/skills/adia-ui-llm/SKILL.md +0 -165
- package/skills/adia-ui-llm/evals/adversarial-corpus.json +0 -75
- package/skills/adia-ui-llm/evals/routing-corpus.json +0 -30
- package/skills/adia-ui-llm/evals/teach-routing-cases.json +0 -73
- package/skills/adia-ui-llm/references/teach-protocol.md +0 -78
- package/skills/adia-ui-llm/scripts/audit-llm-roster.mjs +0 -93
- package/skills/adia-ui-llm/scripts/teach-route.mjs +0 -119
- package/skills/adia-ui-llm/skill.json +0 -33
- package/skills/adia-ui-ops/CHANGELOG.md +0 -291
- package/skills/adia-ui-ops/SKILL.md +0 -401
- package/skills/adia-ui-ops/references/INDEX.md +0 -158
- package/skills/adia-ui-ops/references/audit-cadence.md +0 -263
- package/skills/adia-ui-ops/references/audit-patterns/archive-link-sweep.md +0 -96
- package/skills/adia-ui-ops/references/audit-patterns/audit-history-ledger.md +0 -162
- package/skills/adia-ui-ops/references/audit-patterns/browser-bundle-node-imports.md +0 -154
- package/skills/adia-ui-ops/references/audit-patterns/changelog-unreleased-bloat.md +0 -75
- package/skills/adia-ui-ops/references/audit-patterns/coverage-gaps.md +0 -187
- package/skills/adia-ui-ops/references/audit-patterns/entry-file-coverage.md +0 -122
- package/skills/adia-ui-ops/references/audit-patterns/format-hygiene.md +0 -217
- package/skills/adia-ui-ops/references/audit-patterns/lockstep-versioning.md +0 -113
- package/skills/adia-ui-ops/references/audit-patterns/memory-fragmentation.md +0 -180
- package/skills/adia-ui-ops/references/audit-patterns/orphan-detection.md +0 -202
- package/skills/adia-ui-ops/references/audit-patterns/pointer-validation.md +0 -180
- package/skills/adia-ui-ops/references/audit-patterns/redundancy-detection.md +0 -210
- package/skills/adia-ui-ops/references/audit-patterns/spec-dating-sweep.md +0 -91
- package/skills/adia-ui-ops/references/audit-patterns/stale-content.md +0 -182
- package/skills/adia-ui-ops/references/audit-patterns/staleness-tooling.md +0 -156
- package/skills/adia-ui-ops/references/audit-patterns/token-waste-detection.md +0 -196
- package/skills/adia-ui-ops/references/doc-types/adr-pattern.md +0 -210
- package/skills/adia-ui-ops/references/doc-types/architecture-md.md +0 -190
- package/skills/adia-ui-ops/references/doc-types/changelog.md +0 -183
- package/skills/adia-ui-ops/references/doc-types/decisions-log.md +0 -146
- package/skills/adia-ui-ops/references/doc-types/plan-roadmap.md +0 -182
- package/skills/adia-ui-ops/references/doc-types/postmortem-pattern.md +0 -206
- package/skills/adia-ui-ops/references/genres/prose-and-writing.md +0 -149
- package/skills/adia-ui-ops/references/guidance/context-budget.md +0 -137
- package/skills/adia-ui-ops/references/guidance/llm-doc-writing.md +0 -116
- package/skills/adia-ui-ops/references/guidance/reliability-dial.md +0 -186
- package/skills/adia-ui-ops/references/recipes/adr-introduction.md +0 -211
- package/skills/adia-ui-ops/references/recipes/audit-existing-repo.md +0 -234
- package/skills/adia-ui-ops/references/recipes/cold-start-harvest.md +0 -263
- package/skills/adia-ui-ops/references/recipes/concurrent-learnings-merge.md +0 -158
- package/skills/adia-ui-ops/references/recipes/continuous-learning-loop.md +0 -169
- package/skills/adia-ui-ops/references/recipes/external-reference-verification.md +0 -158
- package/skills/adia-ui-ops/references/recipes/findings-index-readout.md +0 -126
- package/skills/adia-ui-ops/references/recipes/greenfield-setup.md +0 -252
- package/skills/adia-ui-ops/references/recipes/harvest-repo-brain.md +0 -169
- package/skills/adia-ui-ops/references/recipes/import-repo-brain-harvest.md +0 -153
- package/skills/adia-ui-ops/references/recipes/memory-organization.md +0 -182
- package/skills/adia-ui-ops/references/recipes/recommend-then-validate.md +0 -199
- package/skills/adia-ui-ops/references/recipes/self-healing-hooks.md +0 -366
- package/skills/adia-ui-ops/references/recipes/skill-stewardship-loop.md +0 -113
- package/skills/adia-ui-ops/references/standards/agents-md-spec.md +0 -138
- package/skills/adia-ui-ops/references/standards/claude-md-convention.md +0 -123
- package/skills/adia-ui-ops/references/standards/cross-tool-matrix.md +0 -85
- package/skills/adia-ui-ops/references/standards/readme-conventions.md +0 -232
- package/skills/adia-ui-ops/references/teach-protocol.md +0 -215
- package/skills/adia-ui-ops/scripts/audit-ops-roster.mjs +0 -104
- package/skills/adia-ui-ops/skill.json +0 -65
- package/skills/adia-ui-release/CHANGELOG.md +0 -66
- package/skills/adia-ui-release/SKILL.md +0 -323
- package/skills/adia-ui-release/assets/case-studies/2026-05-20-batch-push-v0.6.14-v0.6.15.md +0 -144
- package/skills/adia-ui-release/assets/case-studies/2026-05-20-corpus-drift-remediation-v0.6.15.md +0 -155
- package/skills/adia-ui-release/assets/case-studies/2026-05-20-version-skip-correction-v0.6.12.md +0 -114
- package/skills/adia-ui-release/assets/case-studies/2026-05-21-author-from-scratch-v0.6.18.md +0 -139
- package/skills/adia-ui-release/assets/case-studies/2026-05-21-feedback37-retraction-v0.6.21.md +0 -124
- package/skills/adia-ui-release/assets/case-studies/2026-05-21-fn1-enrichment-pass-v0.6.19.md +0 -125
- package/skills/adia-ui-release/assets/case-studies/2026-05-21-stale-test-detection-v0.6.20.md +0 -142
- package/skills/adia-ui-release/assets/case-studies/2026-05-23-freshness-gate-recovery-v0.6.32.md +0 -97
- package/skills/adia-ui-release/assets/case-studies/2026-05-26-catalog-drift-recurring-v0.6.40.md +0 -147
- package/skills/adia-ui-release/assets/templates/stub-changelog.template.md +0 -22
- package/skills/adia-ui-release/evals/evals.json +0 -164
- package/skills/adia-ui-release/references/changelog-discipline.md +0 -250
- package/skills/adia-ui-release/references/cycle-happy-path.md +0 -520
- package/skills/adia-ui-release/references/exe-deploy.md +0 -149
- package/skills/adia-ui-release/references/gates-catalog.md +0 -778
- package/skills/adia-ui-release/references/independent-package-release.md +0 -129
- package/skills/adia-ui-release/references/ledger-discipline.md +0 -232
- package/skills/adia-ui-release/references/migration-guide-authoring.md +0 -174
- package/skills/adia-ui-release/references/multi-agent-baseline.md +0 -207
- package/skills/adia-ui-release/references/notes-authoring.md +0 -212
- package/skills/adia-ui-release/references/recovery-paths.md +0 -262
- package/skills/adia-ui-release/references/rollup-notes.md +0 -208
- package/skills/adia-ui-release/references/teach-protocol.md +0 -468
- package/skills/adia-ui-release/scripts/audit-gate-roster.mjs +0 -196
- package/skills/adia-ui-release/scripts/make-ledger.mjs +0 -200
- package/skills/adia-ui-release/skill.json +0 -77
- package/skills/dogfood-sweep/CHANGELOG.md +0 -37
- package/skills/dogfood-sweep/README.md +0 -105
- package/skills/dogfood-sweep/SKILL.md +0 -1000
- package/skills/dogfood-sweep/analyze.mjs +0 -600
- package/skills/dogfood-sweep/skill.json +0 -31
- /package/skills/{adia-ui-authoring → adia-author}/references/api-contract.md +0 -0
- /package/skills/{adia-ui-release → adia-release}/scripts/assert-monorepo-root.mjs +0 -0
package/bin/lib/audit-axes.mjs
DELETED
|
@@ -1,555 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* audit-axes.mjs — Universal audit axis library
|
|
4
|
-
*
|
|
5
|
-
* Composable audit functions for rollup-family skill audit scripts.
|
|
6
|
-
* Per-skill `scripts/audit-<name>-roster.mjs` imports and composes these
|
|
7
|
-
* with their own skill-specific axes.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* import * as axes from '${CLAUDE_PLUGIN_ROOT}/bin/lib/audit-axes.mjs';
|
|
11
|
-
* const result = axes.manifestEnforcement({ skillDir, skillMd, skillJson });
|
|
12
|
-
*
|
|
13
|
-
* Each function returns:
|
|
14
|
-
* { axis, axis_num, status: 'ok'|'drift', findings: [...], summary }
|
|
15
|
-
*
|
|
16
|
-
* All path arguments should be absolute.
|
|
17
|
-
*
|
|
18
|
-
* @module audit-axes
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import fs from 'node:fs';
|
|
22
|
-
import path from 'node:path';
|
|
23
|
-
|
|
24
|
-
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
/** Recursively walk a directory, returning relative paths of matching files. */
|
|
27
|
-
function walkDir(dir, { include = null, exclude = ['node_modules', '.git', 'docs'] } = {}) {
|
|
28
|
-
const results = [];
|
|
29
|
-
if (!fs.existsSync(dir)) return results;
|
|
30
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
31
|
-
if (exclude.some(x => entry.name === x)) continue;
|
|
32
|
-
const fullPath = path.join(dir, entry.name);
|
|
33
|
-
if (entry.isDirectory()) {
|
|
34
|
-
results.push(...walkDir(fullPath, { include, exclude }));
|
|
35
|
-
} else {
|
|
36
|
-
if (!include || include.some(ext => entry.name.endsWith(ext))) {
|
|
37
|
-
results.push(fullPath);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return results;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Read file; return '' if missing. */
|
|
45
|
-
function readSafe(p) {
|
|
46
|
-
try { return fs.readFileSync(p, 'utf8'); } catch { return ''; }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Parse YAML frontmatter from a markdown file. Returns {} if none found. */
|
|
50
|
-
function parseFrontmatter(content) {
|
|
51
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
52
|
-
if (!match) return {};
|
|
53
|
-
const result = {};
|
|
54
|
-
for (const line of match[1].split('\n')) {
|
|
55
|
-
const m = line.match(/^(\w+):\s*['"]?(.*?)['"]?\s*$/);
|
|
56
|
-
if (m) result[m[1]] = m[2];
|
|
57
|
-
}
|
|
58
|
-
return result;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Extract all markdown links [text](path) from content, with line numbers. */
|
|
62
|
-
function extractMarkdownLinks(content, filePath) {
|
|
63
|
-
const links = [];
|
|
64
|
-
const lines = content.split('\n');
|
|
65
|
-
let inFence = false;
|
|
66
|
-
for (let i = 0; i < lines.length; i++) {
|
|
67
|
-
const line = lines[i];
|
|
68
|
-
if (/^```/.test(line)) { inFence = !inFence; continue; }
|
|
69
|
-
if (inFence) continue;
|
|
70
|
-
// Strip inline code spans before checking for links (avoids false positives
|
|
71
|
-
// like `[link](references/X.md)` used as format examples in documentation)
|
|
72
|
-
const lineStripped = line.replace(/`[^`]*`/g, '``');
|
|
73
|
-
// Match [text](url) but not [text](#anchor), http links, or mailto
|
|
74
|
-
const re = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
75
|
-
let m;
|
|
76
|
-
while ((m = re.exec(lineStripped)) !== null) {
|
|
77
|
-
const url = m[2];
|
|
78
|
-
if (url.startsWith('http') || url.startsWith('#') || url.startsWith('mailto:')) continue;
|
|
79
|
-
// Skip home-directory paths (shell paths, not relative file paths)
|
|
80
|
-
if (url.startsWith('~/')) continue;
|
|
81
|
-
// Skip template placeholders like <repo>, <name>, <path>
|
|
82
|
-
if (/<[^>]+>/.test(url)) continue;
|
|
83
|
-
// Skip bare single-word paths that look like example placeholders
|
|
84
|
-
if (/^[a-z]+$/.test(url) && !url.includes('/')) continue;
|
|
85
|
-
// Strip fragment
|
|
86
|
-
const cleanUrl = url.split('#')[0];
|
|
87
|
-
if (!cleanUrl) continue;
|
|
88
|
-
links.push({ text: m[1], url: cleanUrl, line: i + 1, file: filePath });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return links;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Make a finding object. */
|
|
95
|
-
function finding(type, message, extra = {}) {
|
|
96
|
-
return { type, message, ...extra };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ─── axes ──────────────────────────────────────────────────────────────────────
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Axis 1 (Manifest enforcement) — recursive walker.
|
|
103
|
-
* Checks that skill.json files[] matches all content files on disk.
|
|
104
|
-
* Reports both directions: on-disk-but-undeclared + declared-but-missing.
|
|
105
|
-
*
|
|
106
|
-
* Scans: references/, scripts/, evals/, assets/ (recursive) + top-level .md + .json (excl. skill.json itself)
|
|
107
|
-
* Excludes: docs/ (refactor-specs), node_modules, .git
|
|
108
|
-
*
|
|
109
|
-
* @param {{ skillDir: string, skillJson: string }} ctx
|
|
110
|
-
*/
|
|
111
|
-
export function manifestEnforcement(ctx) {
|
|
112
|
-
const { skillDir, skillJson } = ctx;
|
|
113
|
-
const pkg = JSON.parse(readSafe(skillJson) || '{}');
|
|
114
|
-
const declared = new Set((pkg.files || []).map(f => path.resolve(skillDir, f)));
|
|
115
|
-
|
|
116
|
-
// Walk the skill directory for all content files
|
|
117
|
-
const onDisk = new Set();
|
|
118
|
-
|
|
119
|
-
// Top-level: *.md, *.json (except node_modules, docs/)
|
|
120
|
-
for (const entry of fs.readdirSync(skillDir, { withFileTypes: true })) {
|
|
121
|
-
if (!entry.isFile()) continue;
|
|
122
|
-
if (!/\.(md|json|mjs|js)$/.test(entry.name)) continue;
|
|
123
|
-
onDisk.add(path.join(skillDir, entry.name));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Subdirectories: references/, scripts/, evals/, assets/ — recursive, excluding docs/
|
|
127
|
-
for (const subDir of ['references', 'scripts', 'evals', 'assets']) {
|
|
128
|
-
const full = path.join(skillDir, subDir);
|
|
129
|
-
for (const f of walkDir(full, { include: ['.md', '.json', '.mjs', '.js', '.ts'] })) {
|
|
130
|
-
onDisk.add(f);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const undeclared = [...onDisk].filter(f => !declared.has(f))
|
|
135
|
-
.map(f => path.relative(skillDir, f));
|
|
136
|
-
const missing = [...declared].filter(f => !onDisk.has(f))
|
|
137
|
-
.map(f => path.relative(skillDir, f));
|
|
138
|
-
|
|
139
|
-
const status = (undeclared.length + missing.length) > 0 ? 'drift' : 'ok';
|
|
140
|
-
const findings = [
|
|
141
|
-
...undeclared.map(f => finding('undeclared', `on disk but not in files[]: ${f}`, { path: f })),
|
|
142
|
-
...missing.map(f => finding('missing', `in files[] but not on disk: ${f}`, { path: f })),
|
|
143
|
-
];
|
|
144
|
-
return {
|
|
145
|
-
axis: 'manifestEnforcement',
|
|
146
|
-
axis_num: 1,
|
|
147
|
-
status,
|
|
148
|
-
findings,
|
|
149
|
-
summary: `${undeclared.length} undeclared, ${missing.length} missing`,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Axis 2 (Reference graph) — resolves markdown links in SKILL.md (and
|
|
155
|
-
* optionally reference files). Reports links that don't resolve to an
|
|
156
|
-
* existing file. Only checks relative links (ignores http://, #anchors,
|
|
157
|
-
* mailto:, ~/ home paths, template placeholders).
|
|
158
|
-
*
|
|
159
|
-
* By default only scans SKILL.md. Pass `includeRefs: true` to also scan
|
|
160
|
-
* reference files (reference files often contain example paths that produce
|
|
161
|
-
* false positives — use with judgment).
|
|
162
|
-
*
|
|
163
|
-
* @param {{ skillDir: string, skillMd: string, includeRefs?: boolean }} ctx
|
|
164
|
-
*/
|
|
165
|
-
export function referenceGraph(ctx) {
|
|
166
|
-
const { skillDir, skillMd, includeRefs = false } = ctx;
|
|
167
|
-
const mdFiles = [skillMd];
|
|
168
|
-
if (includeRefs) {
|
|
169
|
-
mdFiles.push(...walkDir(path.join(skillDir, 'references'), { include: ['.md'] }));
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const findings = [];
|
|
173
|
-
for (const mdFile of mdFiles) {
|
|
174
|
-
const content = readSafe(mdFile);
|
|
175
|
-
const links = extractMarkdownLinks(content, mdFile);
|
|
176
|
-
for (const { url, line, file } of links) {
|
|
177
|
-
// Resolve relative to the containing .md file's directory
|
|
178
|
-
const resolved = path.resolve(path.dirname(file), url);
|
|
179
|
-
if (!fs.existsSync(resolved)) {
|
|
180
|
-
const relFile = path.relative(skillDir, file);
|
|
181
|
-
const relResolved = path.relative(skillDir, resolved);
|
|
182
|
-
findings.push(finding('broken-link',
|
|
183
|
-
`${relFile}:${line} → ${url} (resolves to ${relResolved}, not found)`,
|
|
184
|
-
{ file: relFile, line, url }
|
|
185
|
-
));
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
axis: 'referenceGraph',
|
|
192
|
-
axis_num: 2,
|
|
193
|
-
status: findings.length > 0 ? 'drift' : 'ok',
|
|
194
|
-
findings,
|
|
195
|
-
summary: `${findings.length} broken link(s)`,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Axis 3 (Capability-menu drift) — parses §ColdStartTriage table rows and
|
|
201
|
-
* checks that each referenced entry-reference path exists on disk.
|
|
202
|
-
* Handles both `[label](path)` link format and bare path format.
|
|
203
|
-
*
|
|
204
|
-
* @param {{ skillDir: string, skillMd: string }} ctx
|
|
205
|
-
*/
|
|
206
|
-
export function capabilityMenuDrift(ctx) {
|
|
207
|
-
const { skillDir, skillMd } = ctx;
|
|
208
|
-
const content = readSafe(skillMd);
|
|
209
|
-
|
|
210
|
-
// Find §ColdStartTriage section
|
|
211
|
-
const sectionMatch = content.match(
|
|
212
|
-
/##\s+§ColdStartTriage[\s\S]*?(?=\n##\s+|$)/
|
|
213
|
-
);
|
|
214
|
-
if (!sectionMatch) {
|
|
215
|
-
return {
|
|
216
|
-
axis: 'capabilityMenuDrift',
|
|
217
|
-
axis_num: 3,
|
|
218
|
-
status: 'ok',
|
|
219
|
-
findings: [],
|
|
220
|
-
summary: 'No §ColdStartTriage section found (skip)',
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const section = sectionMatch[0];
|
|
225
|
-
// Extract all markdown links within the section
|
|
226
|
-
const links = extractMarkdownLinks(section, skillMd);
|
|
227
|
-
// Also look for bare `references/X.md` patterns in the section (not in links)
|
|
228
|
-
const barePaths = [...section.matchAll(/`(references\/[^`]+\.md)`/g)]
|
|
229
|
-
.map(m => m[1]);
|
|
230
|
-
|
|
231
|
-
const findings = [];
|
|
232
|
-
|
|
233
|
-
for (const { url, text } of links) {
|
|
234
|
-
if (!url.startsWith('references/') && !url.startsWith('../')) continue;
|
|
235
|
-
const resolved = path.resolve(skillDir, url);
|
|
236
|
-
if (!fs.existsSync(resolved)) {
|
|
237
|
-
findings.push(finding('missing-entry-ref',
|
|
238
|
-
`Mode entry ref missing: [${text}](${url})`,
|
|
239
|
-
{ url }
|
|
240
|
-
));
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
for (const p of barePaths) {
|
|
245
|
-
const resolved = path.join(skillDir, p);
|
|
246
|
-
if (!fs.existsSync(resolved)) {
|
|
247
|
-
findings.push(finding('missing-entry-ref',
|
|
248
|
-
`Bare path in §ColdStartTriage not found: ${p}`,
|
|
249
|
-
{ url: p }
|
|
250
|
-
));
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return {
|
|
255
|
-
axis: 'capabilityMenuDrift',
|
|
256
|
-
axis_num: 3,
|
|
257
|
-
status: findings.length > 0 ? 'drift' : 'ok',
|
|
258
|
-
findings,
|
|
259
|
-
summary: `${findings.length} missing entry-reference path(s)`,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Axis 4 (Version-literal parity) — checks that SKILL.md frontmatter version
|
|
265
|
-
* matches skill.json version. Also scans the §Status section body for stale
|
|
266
|
-
* version literals that don't match.
|
|
267
|
-
*
|
|
268
|
-
* @param {{ skillDir: string, skillMd: string, skillJson: string }} ctx
|
|
269
|
-
*/
|
|
270
|
-
export function versionLiteralParity(ctx) {
|
|
271
|
-
const { skillMd, skillJson } = ctx;
|
|
272
|
-
const content = readSafe(skillMd);
|
|
273
|
-
const pkg = JSON.parse(readSafe(skillJson) || '{}');
|
|
274
|
-
const jsonVersion = pkg.version || null;
|
|
275
|
-
|
|
276
|
-
const findings = [];
|
|
277
|
-
|
|
278
|
-
if (!jsonVersion) {
|
|
279
|
-
findings.push(finding('no-version', 'skill.json has no version field'));
|
|
280
|
-
return { axis: 'versionLiteralParity', axis_num: 4, status: 'drift', findings, summary: 'no version in skill.json' };
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Check frontmatter version
|
|
284
|
-
const fm = parseFrontmatter(content);
|
|
285
|
-
if (fm.version && fm.version !== jsonVersion) {
|
|
286
|
-
findings.push(finding('frontmatter-drift',
|
|
287
|
-
`SKILL.md frontmatter version: ${fm.version} ≠ skill.json: ${jsonVersion}`
|
|
288
|
-
));
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Scan §Status section body for "Version X.Y.Z" or "vX.Y.Z" stale mentions
|
|
292
|
-
const statusMatch = content.match(/##\s+§Status([\s\S]*?)(?=\n##\s+|$)/);
|
|
293
|
-
if (statusMatch) {
|
|
294
|
-
const statusBody = statusMatch[1];
|
|
295
|
-
// Find any version that looks like a semver but isn't the current one
|
|
296
|
-
const versionRe = /\bv?(\d+\.\d+\.\d+)\b/g;
|
|
297
|
-
let m;
|
|
298
|
-
while ((m = versionRe.exec(statusBody)) !== null) {
|
|
299
|
-
const found = m[1];
|
|
300
|
-
if (found !== jsonVersion) {
|
|
301
|
-
findings.push(finding('status-section-stale',
|
|
302
|
-
`§Status body contains version ${found} ≠ skill.json ${jsonVersion} — §Status should only point at CHANGELOG.md`
|
|
303
|
-
));
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
axis: 'versionLiteralParity',
|
|
310
|
-
axis_num: 4,
|
|
311
|
-
status: findings.length > 0 ? 'drift' : 'ok',
|
|
312
|
-
findings,
|
|
313
|
-
summary: findings.length > 0 ? `${findings.length} version-literal mismatch(es)` : `all literals match ${jsonVersion}`,
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Axis 5 (Phase-label absence) — searches SKILL.md body for stale phase
|
|
319
|
-
* annotations: "(Phase 1)", "(Phase 2 — planned)", "Phase 4 (v1.0.0, this)",
|
|
320
|
-
* etc. These should have been removed as part of Round 0 cleanup.
|
|
321
|
-
*
|
|
322
|
-
* @param {{ skillMd: string }} ctx
|
|
323
|
-
*/
|
|
324
|
-
export function phaseLabelAbsence(ctx) {
|
|
325
|
-
const { skillMd } = ctx;
|
|
326
|
-
const content = readSafe(skillMd);
|
|
327
|
-
const lines = content.split('\n');
|
|
328
|
-
|
|
329
|
-
const findings = [];
|
|
330
|
-
const phaseRe = /\(Phase\s+\d+[\w\s—.]*\)|Phase\s+\d+\s*\(v\d+\.\d+\.\d+|Phase\s+\d+\s+references\s*\(planned\)/i;
|
|
331
|
-
|
|
332
|
-
for (let i = 0; i < lines.length; i++) {
|
|
333
|
-
if (phaseRe.test(lines[i])) {
|
|
334
|
-
findings.push(finding('phase-label',
|
|
335
|
-
`Line ${i + 1}: stale phase annotation — ${lines[i].trim()}`,
|
|
336
|
-
{ line: i + 1, content: lines[i].trim() }
|
|
337
|
-
));
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return {
|
|
342
|
-
axis: 'phaseLabelAbsence',
|
|
343
|
-
axis_num: 5,
|
|
344
|
-
status: findings.length > 0 ? 'drift' : 'ok',
|
|
345
|
-
findings,
|
|
346
|
-
summary: `${findings.length} stale phase label(s)`,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Axis 6 (Fence-leak detection) — scans SKILL.md for H2/H3 headings inside
|
|
352
|
-
* fenced code blocks that are NOT ``` markdown / md / mdx ``` type.
|
|
353
|
-
* These are typically fence-close mistakes where a heading leaked into
|
|
354
|
-
* a code block.
|
|
355
|
-
*
|
|
356
|
-
* @param {{ skillMd: string }} ctx
|
|
357
|
-
*/
|
|
358
|
-
export function fenceLeakDetection(ctx) {
|
|
359
|
-
const { skillMd } = ctx;
|
|
360
|
-
const content = readSafe(skillMd);
|
|
361
|
-
const lines = content.split('\n');
|
|
362
|
-
|
|
363
|
-
const findings = [];
|
|
364
|
-
let inFence = false;
|
|
365
|
-
let fenceLang = '';
|
|
366
|
-
let fenceStart = 0;
|
|
367
|
-
|
|
368
|
-
for (let i = 0; i < lines.length; i++) {
|
|
369
|
-
const line = lines[i];
|
|
370
|
-
const fenceOpen = line.match(/^```(\w*)/);
|
|
371
|
-
|
|
372
|
-
if (fenceOpen && !inFence) {
|
|
373
|
-
inFence = true;
|
|
374
|
-
fenceLang = fenceOpen[1].toLowerCase();
|
|
375
|
-
fenceStart = i + 1;
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
if (inFence && line.startsWith('```') && !line.startsWith('````')) {
|
|
380
|
-
inFence = false;
|
|
381
|
-
fenceLang = '';
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (inFence) {
|
|
386
|
-
// Headings inside markdown fences are intentional (templates)
|
|
387
|
-
if (['markdown', 'md', 'mdx'].includes(fenceLang)) continue;
|
|
388
|
-
// Headings inside non-markdown fences are likely fence-leak bugs
|
|
389
|
-
if (/^#{2,3}\s/.test(line)) {
|
|
390
|
-
findings.push(finding('fence-leak',
|
|
391
|
-
`Line ${i + 1}: heading inside \`\`\`${fenceLang}\`\`\` fence — ${line.trim()}`,
|
|
392
|
-
{ line: i + 1, fenceLang, content: line.trim() }
|
|
393
|
-
));
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return {
|
|
399
|
-
axis: 'fenceLeakDetection',
|
|
400
|
-
axis_num: 6,
|
|
401
|
-
status: findings.length > 0 ? 'drift' : 'ok',
|
|
402
|
-
findings,
|
|
403
|
-
summary: `${findings.length} fence-leak heading(s)`,
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Axis 7 (Content currency) — verifies that paths in skill.json files[]
|
|
409
|
-
* actually exist. A missing file means the manifest is stale (probably a
|
|
410
|
-
* file was moved or deleted without updating the manifest).
|
|
411
|
-
* Note: this overlaps with manifestEnforcement's "declared-but-missing"
|
|
412
|
-
* direction; use that for bidirectional checks, this for declaration-only.
|
|
413
|
-
*
|
|
414
|
-
* @param {{ skillDir: string, skillJson: string }} ctx
|
|
415
|
-
*/
|
|
416
|
-
export function contentCurrency(ctx) {
|
|
417
|
-
const { skillDir, skillJson } = ctx;
|
|
418
|
-
const pkg = JSON.parse(readSafe(skillJson) || '{}');
|
|
419
|
-
const declared = pkg.files || [];
|
|
420
|
-
|
|
421
|
-
const findings = [];
|
|
422
|
-
for (const relPath of declared) {
|
|
423
|
-
const full = path.resolve(skillDir, relPath);
|
|
424
|
-
if (!fs.existsSync(full)) {
|
|
425
|
-
findings.push(finding('missing-declared',
|
|
426
|
-
`Declared in files[] but not on disk: ${relPath}`,
|
|
427
|
-
{ path: relPath }
|
|
428
|
-
));
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return {
|
|
433
|
-
axis: 'contentCurrency',
|
|
434
|
-
axis_num: 7,
|
|
435
|
-
status: findings.length > 0 ? 'drift' : 'ok',
|
|
436
|
-
findings,
|
|
437
|
-
summary: `${findings.length} declared-but-missing file(s)`,
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Axis 8 (CLI helper currency) — scans SKILL.md for `npm run X` and
|
|
443
|
-
* `node scripts/Y` patterns and checks they resolve. Scripts labeled
|
|
444
|
-
* `(substrate-only)` or `(monorepo only)` are skipped.
|
|
445
|
-
* Requires repoRoot to resolve package.json for npm run checks.
|
|
446
|
-
*
|
|
447
|
-
* @param {{ skillDir: string, skillMd: string, repoRoot?: string }} ctx
|
|
448
|
-
*/
|
|
449
|
-
export function cliHelperCurrency(ctx) {
|
|
450
|
-
const { skillDir, skillMd, repoRoot } = ctx;
|
|
451
|
-
const content = readSafe(skillMd);
|
|
452
|
-
|
|
453
|
-
const findings = [];
|
|
454
|
-
|
|
455
|
-
// Parse package.json scripts if repoRoot provided
|
|
456
|
-
let pkgScripts = null;
|
|
457
|
-
if (repoRoot) {
|
|
458
|
-
try {
|
|
459
|
-
const pkgJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
|
|
460
|
-
pkgScripts = pkgJson.scripts || {};
|
|
461
|
-
} catch { /* repoRoot doesn't have package.json */ }
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Skip lines with (substrate-only) or (monorepo only) annotations
|
|
465
|
-
const lines = content.split('\n');
|
|
466
|
-
for (let i = 0; i < lines.length; i++) {
|
|
467
|
-
const line = lines[i];
|
|
468
|
-
if (/(substrate.only|monorepo.only)/i.test(line)) continue;
|
|
469
|
-
// Skip comment lines
|
|
470
|
-
if (/^\s*(#|\/\/)/.test(line)) continue;
|
|
471
|
-
|
|
472
|
-
// npm run X
|
|
473
|
-
const npmRe = /`npm run ([\w:.-]+)`/g;
|
|
474
|
-
let m;
|
|
475
|
-
while ((m = npmRe.exec(line)) !== null) {
|
|
476
|
-
const scriptName = m[1];
|
|
477
|
-
if (pkgScripts && !pkgScripts[scriptName]) {
|
|
478
|
-
findings.push(finding('missing-npm-script',
|
|
479
|
-
`Line ${i + 1}: npm run ${scriptName} not found in package.json scripts`,
|
|
480
|
-
{ line: i + 1, script: scriptName }
|
|
481
|
-
));
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// node scripts/X.mjs or node skills/.../scripts/X.mjs
|
|
486
|
-
const nodeRe = /`node\s+(skills\/[^\s`]+|scripts\/[^\s`]+)`/g;
|
|
487
|
-
while ((m = nodeRe.exec(line)) !== null) {
|
|
488
|
-
const scriptPath = m[1];
|
|
489
|
-
// Resolve relative to repoRoot if available, otherwise skillDir
|
|
490
|
-
const base = repoRoot || skillDir;
|
|
491
|
-
const resolved = path.resolve(base, scriptPath);
|
|
492
|
-
if (!fs.existsSync(resolved)) {
|
|
493
|
-
findings.push(finding('missing-script',
|
|
494
|
-
`Line ${i + 1}: node ${scriptPath} — file not found`,
|
|
495
|
-
{ line: i + 1, scriptPath }
|
|
496
|
-
));
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
return {
|
|
502
|
-
axis: 'cliHelperCurrency',
|
|
503
|
-
axis_num: 8,
|
|
504
|
-
status: findings.length > 0 ? 'drift' : 'ok',
|
|
505
|
-
findings,
|
|
506
|
-
summary: `${findings.length} missing CLI helper(s)`,
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// ─── convenience: run a standard set ──────────────────────────────────────────
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Run all 8 universal axes and return results.
|
|
514
|
-
* Per-skill scripts call this then append their own axes.
|
|
515
|
-
*
|
|
516
|
-
* @param {{ skillDir: string, skillMd: string, skillJson: string, repoRoot?: string }} ctx
|
|
517
|
-
* @returns {{ results: object[], driftCount: number }}
|
|
518
|
-
*/
|
|
519
|
-
export function runUniversalAxes(ctx) {
|
|
520
|
-
const axeFns = [
|
|
521
|
-
manifestEnforcement,
|
|
522
|
-
referenceGraph,
|
|
523
|
-
capabilityMenuDrift,
|
|
524
|
-
versionLiteralParity,
|
|
525
|
-
phaseLabelAbsence,
|
|
526
|
-
fenceLeakDetection,
|
|
527
|
-
contentCurrency,
|
|
528
|
-
cliHelperCurrency,
|
|
529
|
-
];
|
|
530
|
-
|
|
531
|
-
const results = axeFns.map(fn => fn(ctx));
|
|
532
|
-
const driftCount = results.filter(r => r.status === 'drift').length;
|
|
533
|
-
return { results, driftCount };
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Format a results array as human-readable text.
|
|
538
|
-
* @param {object[]} results
|
|
539
|
-
* @param {{ strict?: boolean, json?: boolean }} opts
|
|
540
|
-
*/
|
|
541
|
-
export function formatResults(results, opts = {}) {
|
|
542
|
-
if (opts.json) return JSON.stringify({ results }, null, 2);
|
|
543
|
-
|
|
544
|
-
const lines = [];
|
|
545
|
-
for (const r of results) {
|
|
546
|
-
const icon = r.status === 'ok' ? '✓' : '✗';
|
|
547
|
-
lines.push(`${icon} ${r.axis}: ${r.summary}`);
|
|
548
|
-
if (r.status === 'drift' && r.findings.length > 0) {
|
|
549
|
-
for (const f of r.findings) {
|
|
550
|
-
lines.push(` ${f.type}: ${f.message}`);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
return lines.join('\n');
|
|
555
|
-
}
|