@skill-graph/cli 0.5.6
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/CHANGELOG.md +247 -0
- package/LICENSE +200 -0
- package/NOTICE +62 -0
- package/README.md +398 -0
- package/SKILL_GRAPH.md +443 -0
- package/bin/skill-graph.js +374 -0
- package/docs/ADOPTION.md +117 -0
- package/docs/CONFORMANCE.md +66 -0
- package/docs/PRIMER.md +384 -0
- package/docs/QUICKSTART-30MIN.md +333 -0
- package/docs/ROUTING-METRICS.md +120 -0
- package/docs/SKILL-MD-FORMAT-COMPATIBILITY.md +127 -0
- package/docs/SKILL_AUDIT_CHECKLIST.md +199 -0
- package/docs/SKILL_AUDIT_LOOP.md +195 -0
- package/docs/SKILL_METADATA_PROTOCOL.md +609 -0
- package/docs/_archived/marketplace-publication-priority-2026-05-18.md +239 -0
- package/docs/adr/0001-predicate-set.md +69 -0
- package/docs/adr/0002-json-ld-context.md +82 -0
- package/docs/adr/0003-ontoclean-rigidity-tags.md +65 -0
- package/docs/adr/0004-persistent-identifiers.md +74 -0
- package/docs/adr/0005-freshness-consolidation.md +70 -0
- package/docs/adr/0006-revise-predicate-rename.md +105 -0
- package/docs/adr/0007-audit-loop-cadence.md +99 -0
- package/docs/adr/0008-skill-surface-split-and-curation-policy.md +93 -0
- package/docs/category-consumers.md +168 -0
- package/docs/concept-map.md +194 -0
- package/docs/diagrams/drift-states.mmd +21 -0
- package/docs/diagrams/manifest-pipeline.mmd +25 -0
- package/docs/diagrams/routing-harness.mmd +41 -0
- package/docs/diagrams/starter-graph.mmd +53 -0
- package/docs/field-decision-guide.md +315 -0
- package/docs/field-rationale.md +211 -0
- package/docs/field-reference.generated.md +624 -0
- package/docs/field-reference.md +1426 -0
- package/docs/glossary.md +190 -0
- package/docs/head-noun-glossary.md +63 -0
- package/docs/images/audit-phases.png +0 -0
- package/docs/images/drift-states.png +0 -0
- package/docs/images/graded-mode.png +0 -0
- package/docs/images/manifest-pipeline.png +0 -0
- package/docs/images/routing-harness.png +0 -0
- package/docs/images/skill-anatomy.png +0 -0
- package/docs/images/starter-graph.png +0 -0
- package/docs/images/system-model.png +0 -0
- package/docs/integrations/github-actions.md +155 -0
- package/docs/manifest-field-mapping.md +443 -0
- package/docs/marketplace-publication-queue.generated.md +240 -0
- package/docs/marketplace-release-agent-prompt.md +82 -0
- package/docs/marketplace-skill-candidate-list.md +272 -0
- package/docs/marketplace-syndication.md +222 -0
- package/docs/migration-sample-review.md +155 -0
- package/docs/migrations/v4-to-v5.md +168 -0
- package/docs/migrations/v5-to-v6.md +221 -0
- package/docs/name-exceptions.yaml +37 -0
- package/docs/plans/marketplace-p1-public-migration-plan.md +41 -0
- package/docs/plans/multi-root-workspace.md +148 -0
- package/docs/plans/scripts-roadmap.md +107 -0
- package/docs/plans/v4-schema-bump.md +160 -0
- package/docs/plans/wave-2-extraction.md +122 -0
- package/docs/positioning-vs-marketplaces.md +175 -0
- package/docs/proposals/skill-audit-loop-positioning.md +160 -0
- package/docs/quality-doctrine.md +138 -0
- package/docs/recommended-skills.md +150 -0
- package/docs/research/skill-comprehension-eval-research.md +1830 -0
- package/docs/research/skill-retrieval-evidence.md +66 -0
- package/docs/skill-metadata-protocol.md +471 -0
- package/docs/skills-sh-maintainer-cleanup-request.md +80 -0
- package/examples/audits/a11y/findings.md +52 -0
- package/examples/audits/a11y/scorecard.md +21 -0
- package/examples/audits/a11y/verdict.md +44 -0
- package/examples/audits/debugging/findings.md +59 -0
- package/examples/audits/debugging/scorecard.md +22 -0
- package/examples/audits/debugging/verdict.md +33 -0
- package/examples/audits/documentation/findings.md +59 -0
- package/examples/audits/documentation/scorecard.md +22 -0
- package/examples/audits/documentation/verdict.md +33 -0
- package/examples/evals/a11y.json +140 -0
- package/examples/evals/api-design.json +52 -0
- package/examples/evals/code-review.json +52 -0
- package/examples/evals/data-modeling.json +52 -0
- package/examples/evals/database-migration.json +52 -0
- package/examples/evals/debugging.json +118 -0
- package/examples/evals/dependency-architecture.json +52 -0
- package/examples/evals/design-system-architecture.json +52 -0
- package/examples/evals/error-tracking.json +52 -0
- package/examples/evals/event-contract-design.json +52 -0
- package/examples/evals/form-ux-architecture.json +52 -0
- package/examples/evals/framework-fit-analysis.json +52 -0
- package/examples/evals/graph-audit.json +139 -0
- package/examples/evals/information-architecture.json +52 -0
- package/examples/evals/interaction-feedback.json +52 -0
- package/examples/evals/interaction-patterns.json +52 -0
- package/examples/evals/layout-composition.json +52 -0
- package/examples/evals/lint-overlay.json +117 -0
- package/examples/evals/microcopy.json +52 -0
- package/examples/evals/observability-modeling.json +52 -0
- package/examples/evals/pattern-recognition.json +96 -0
- package/examples/evals/performance-engineering.json +52 -0
- package/examples/evals/refactor.json +128 -0
- package/examples/evals/semiotics.json +52 -0
- package/examples/evals/skill-infrastructure.json +96 -0
- package/examples/evals/skill-router.json +140 -0
- package/examples/evals/skill-router.routing.json +113 -0
- package/examples/evals/system-interface-contracts.json +52 -0
- package/examples/evals/task-analysis.json +52 -0
- package/examples/evals/testing-strategy.json +118 -0
- package/examples/evals/type-safety.json +249 -0
- package/examples/evals/visual-design-foundations.json +52 -0
- package/examples/evals/webhook-integration.json +52 -0
- package/examples/exports/a11y.skill-md.md +80 -0
- package/examples/exports/debugging.skill-md.md +80 -0
- package/examples/exports/refactor.skill-md.md +78 -0
- package/examples/exports/testing-strategy.skill-md.md +81 -0
- package/examples/projects/markdown-static-site/README.md +115 -0
- package/examples/projects/markdown-static-site/skills/content-source-router/SKILL.md +131 -0
- package/examples/projects/markdown-static-site/skills/image-optimization-pipeline-config/SKILL.md +132 -0
- package/examples/projects/markdown-static-site/skills/link-rot-detection/SKILL.md +103 -0
- package/examples/projects/markdown-static-site/skills/markdown-post-frontmatter-validation/SKILL.md +133 -0
- package/examples/projects/markdown-static-site/skills/migrate-posts-to-v2-frontmatter/SKILL.md +140 -0
- package/examples/projects/saas-stripe-postgres/README.md +208 -0
- package/examples/projects/saas-stripe-postgres/db/migrations/0004_canonicalize_orders.sql +37 -0
- package/examples/projects/saas-stripe-postgres/db/schema.sql +112 -0
- package/examples/projects/saas-stripe-postgres/skills/migrate-orders-to-canonical-schema/SKILL.md +149 -0
- package/examples/projects/saas-stripe-postgres/skills/nextjs-server-action-validation/SKILL.md +154 -0
- package/examples/projects/saas-stripe-postgres/skills/payment-provider-router/SKILL.md +153 -0
- package/examples/projects/saas-stripe-postgres/skills/postgres-rls-pattern/SKILL.md +163 -0
- package/examples/projects/saas-stripe-postgres/skills/stripe-webhook-signature-verification/SKILL.md +137 -0
- package/examples/protocol/skill-metadata-template.md +301 -0
- package/examples/protocol/skills.manifest.sample.json +13245 -0
- package/examples/skill-metadata-template.md +317 -0
- package/examples/skills.manifest.sample.json +13519 -0
- package/examples/tests/v3-1-skos-fixture/SKILL.md +93 -0
- package/marketplace/README.md +17 -0
- package/marketplace/skills/a11y/SKILL.md +66 -0
- package/marketplace/skills/acid-fundamentals/SKILL.md +106 -0
- package/marketplace/skills/agent-engineering/SKILL.md +386 -0
- package/marketplace/skills/agent-eval-design/SKILL.md +55 -0
- package/marketplace/skills/ai-native-development/SKILL.md +294 -0
- package/marketplace/skills/api-design/SKILL.md +60 -0
- package/marketplace/skills/architecture-decision-records/SKILL.md +55 -0
- package/marketplace/skills/background-jobs/SKILL.md +265 -0
- package/marketplace/skills/bounded-context-mapping/SKILL.md +55 -0
- package/marketplace/skills/cap-theorem-tradeoffs/SKILL.md +127 -0
- package/marketplace/skills/client-server-boundary/SKILL.md +187 -0
- package/marketplace/skills/code-review/SKILL.md +120 -0
- package/marketplace/skills/color-system-design/SKILL.md +43 -0
- package/marketplace/skills/component-architecture/SKILL.md +126 -0
- package/marketplace/skills/compression/SKILL.md +112 -0
- package/marketplace/skills/conceptual-modeling/SKILL.md +181 -0
- package/marketplace/skills/connection-pooling/SKILL.md +105 -0
- package/marketplace/skills/constraint-awareness/SKILL.md +287 -0
- package/marketplace/skills/content-monitor/SKILL.md +209 -0
- package/marketplace/skills/context-engineering/SKILL.md +320 -0
- package/marketplace/skills/context-graph/SKILL.md +174 -0
- package/marketplace/skills/context-management/SKILL.md +174 -0
- package/marketplace/skills/context-window/SKILL.md +239 -0
- package/marketplace/skills/contract-testing/SKILL.md +120 -0
- package/marketplace/skills/cron-scheduling/SKILL.md +223 -0
- package/marketplace/skills/dark-mode-implementation/SKILL.md +47 -0
- package/marketplace/skills/data-modeling/SKILL.md +59 -0
- package/marketplace/skills/data-modeling-fundamentals/SKILL.md +117 -0
- package/marketplace/skills/database-migration/SKILL.md +429 -0
- package/marketplace/skills/debugging/SKILL.md +67 -0
- package/marketplace/skills/dependency-architecture/SKILL.md +58 -0
- package/marketplace/skills/design-module-composition/SKILL.md +43 -0
- package/marketplace/skills/design-system-architecture/SKILL.md +61 -0
- package/marketplace/skills/design-thinking/SKILL.md +44 -0
- package/marketplace/skills/diagnosis/SKILL.md +296 -0
- package/marketplace/skills/diff-analysis/SKILL.md +188 -0
- package/marketplace/skills/e2e-test-design/SKILL.md +113 -0
- package/marketplace/skills/entity-relationship-modeling/SKILL.md +218 -0
- package/marketplace/skills/epistemic-grounding/SKILL.md +112 -0
- package/marketplace/skills/error-boundary/SKILL.md +235 -0
- package/marketplace/skills/error-tracking/SKILL.md +261 -0
- package/marketplace/skills/eval-driven-development/SKILL.md +147 -0
- package/marketplace/skills/evaluation/SKILL.md +113 -0
- package/marketplace/skills/event-contract-design/SKILL.md +60 -0
- package/marketplace/skills/event-storming/SKILL.md +56 -0
- package/marketplace/skills/form-ux-architecture/SKILL.md +60 -0
- package/marketplace/skills/framework-fit-analysis/SKILL.md +59 -0
- package/marketplace/skills/frontend-architecture/SKILL.md +43 -0
- package/marketplace/skills/generative-ui/SKILL.md +118 -0
- package/marketplace/skills/graph-audit/SKILL.md +81 -0
- package/marketplace/skills/guardrails/SKILL.md +118 -0
- package/marketplace/skills/hooks-patterns/SKILL.md +185 -0
- package/marketplace/skills/http-semantics/SKILL.md +136 -0
- package/marketplace/skills/ideation/SKILL.md +41 -0
- package/marketplace/skills/indexing-strategy/SKILL.md +108 -0
- package/marketplace/skills/information-architecture/SKILL.md +59 -0
- package/marketplace/skills/integration-test-design/SKILL.md +111 -0
- package/marketplace/skills/intent-recognition/SKILL.md +136 -0
- package/marketplace/skills/interaction-feedback/SKILL.md +59 -0
- package/marketplace/skills/interaction-patterns/SKILL.md +59 -0
- package/marketplace/skills/journey-mapping/SKILL.md +41 -0
- package/marketplace/skills/keywords/SKILL.md +213 -0
- package/marketplace/skills/knowledge-modeling/SKILL.md +232 -0
- package/marketplace/skills/layout-composition/SKILL.md +59 -0
- package/marketplace/skills/linguistics/SKILL.md +429 -0
- package/marketplace/skills/lint-overlay/SKILL.md +76 -0
- package/marketplace/skills/mental-models/SKILL.md +126 -0
- package/marketplace/skills/merge-queue/SKILL.md +94 -0
- package/marketplace/skills/methodology/SKILL.md +317 -0
- package/marketplace/skills/microcopy/SKILL.md +232 -0
- package/marketplace/skills/middleware-patterns/SKILL.md +363 -0
- package/marketplace/skills/mobile-responsive-ux/SKILL.md +287 -0
- package/marketplace/skills/mutation-testing/SKILL.md +112 -0
- package/marketplace/skills/naming-conventions/SKILL.md +112 -0
- package/marketplace/skills/observability-modeling/SKILL.md +59 -0
- package/marketplace/skills/ontology-modeling/SKILL.md +67 -0
- package/marketplace/skills/owasp-security/SKILL.md +153 -0
- package/marketplace/skills/pattern-recognition/SKILL.md +472 -0
- package/marketplace/skills/performance-budgets/SKILL.md +185 -0
- package/marketplace/skills/performance-engineering/SKILL.md +58 -0
- package/marketplace/skills/performance-testing/SKILL.md +125 -0
- package/marketplace/skills/printify/SKILL.md +42 -0
- package/marketplace/skills/prioritization/SKILL.md +118 -0
- package/marketplace/skills/problem-framing/SKILL.md +41 -0
- package/marketplace/skills/problem-locating-solving/SKILL.md +203 -0
- package/marketplace/skills/project-knowledge-extraction/SKILL.md +54 -0
- package/marketplace/skills/prompt-craft/SKILL.md +134 -0
- package/marketplace/skills/prompt-injection-defense/SKILL.md +132 -0
- package/marketplace/skills/property-based-testing/SKILL.md +100 -0
- package/marketplace/skills/prototyping/SKILL.md +43 -0
- package/marketplace/skills/query-optimization/SKILL.md +144 -0
- package/marketplace/skills/real-time-updates/SKILL.md +324 -0
- package/marketplace/skills/ref-patterns/SKILL.md +284 -0
- package/marketplace/skills/refactor/SKILL.md +65 -0
- package/marketplace/skills/rendering-models/SKILL.md +142 -0
- package/marketplace/skills/replication-patterns/SKILL.md +110 -0
- package/marketplace/skills/research-synthesis/SKILL.md +41 -0
- package/marketplace/skills/route-handler-design/SKILL.md +347 -0
- package/marketplace/skills/schema-evolution/SKILL.md +140 -0
- package/marketplace/skills/security-fundamentals/SKILL.md +139 -0
- package/marketplace/skills/semantic-center/SKILL.md +194 -0
- package/marketplace/skills/semantic-relations/SKILL.md +250 -0
- package/marketplace/skills/semantics/SKILL.md +366 -0
- package/marketplace/skills/semiotics/SKILL.md +230 -0
- package/marketplace/skills/seo-strategy/SKILL.md +260 -0
- package/marketplace/skills/server-actions-design/SKILL.md +243 -0
- package/marketplace/skills/server-components-design/SKILL.md +190 -0
- package/marketplace/skills/sharding-strategy/SKILL.md +123 -0
- package/marketplace/skills/shopify/SKILL.md +42 -0
- package/marketplace/skills/skill-infrastructure/SKILL.md +320 -0
- package/marketplace/skills/skill-router/SKILL.md +71 -0
- package/marketplace/skills/skill-scaffold/SKILL.md +105 -0
- package/marketplace/skills/snapshot-testing/SKILL.md +120 -0
- package/marketplace/skills/spec-driven-development/SKILL.md +148 -0
- package/marketplace/skills/state-machine-modeling/SKILL.md +56 -0
- package/marketplace/skills/state-management/SKILL.md +134 -0
- package/marketplace/skills/streaming-architecture/SKILL.md +194 -0
- package/marketplace/skills/summarization/SKILL.md +156 -0
- package/marketplace/skills/suspense-patterns/SKILL.md +265 -0
- package/marketplace/skills/system-interface-contracts/SKILL.md +59 -0
- package/marketplace/skills/task-analysis/SKILL.md +201 -0
- package/marketplace/skills/taxonomy-design/SKILL.md +66 -0
- package/marketplace/skills/test-coverage-strategy/SKILL.md +108 -0
- package/marketplace/skills/test-doubles-design/SKILL.md +98 -0
- package/marketplace/skills/test-driven-development/SKILL.md +96 -0
- package/marketplace/skills/testing-strategy/SKILL.md +67 -0
- package/marketplace/skills/theme-system-design/SKILL.md +43 -0
- package/marketplace/skills/tool-call-flow/SKILL.md +229 -0
- package/marketplace/skills/tool-call-strategy/SKILL.md +292 -0
- package/marketplace/skills/transaction-isolation/SKILL.md +98 -0
- package/marketplace/skills/type-safety/SKILL.md +177 -0
- package/marketplace/skills/typography-system/SKILL.md +43 -0
- package/marketplace/skills/usability-testing/SKILL.md +43 -0
- package/marketplace/skills/user-research/SKILL.md +43 -0
- package/marketplace/skills/vercel-composition-patterns/SKILL.md +157 -0
- package/marketplace/skills/version-control/SKILL.md +233 -0
- package/marketplace/skills/visual-design-foundations/SKILL.md +59 -0
- package/marketplace/skills/visual-hierarchy/SKILL.md +43 -0
- package/marketplace/skills/webhook-integration/SKILL.md +331 -0
- package/marketplace/skills/writing-humanizer/SKILL.md +380 -0
- package/package.json +67 -0
- package/schemas/manifest.schema.json +811 -0
- package/schemas/manifest.v2.schema.json +164 -0
- package/schemas/manifest.v3.schema.json +758 -0
- package/schemas/manifest.v4.schema.json +755 -0
- package/schemas/manifest.v5.schema.json +755 -0
- package/schemas/manifest.v6.schema.json +811 -0
- package/schemas/skill.context.jsonld +279 -0
- package/schemas/skill.schema.json +919 -0
- package/schemas/skill.v2.schema.json +201 -0
- package/schemas/skill.v3.schema.json +827 -0
- package/schemas/skill.v4.schema.json +822 -0
- package/schemas/skill.v5.schema.json +830 -0
- package/schemas/skill.v6.schema.json +946 -0
- package/schemas/vocabulary/keywords.json +180 -0
- package/schemas/vocabulary/workspace_tags.json +23 -0
- package/scripts/__tests__/migrate-skill-v2-to-v3.test.js +161 -0
- package/scripts/__tests__/migrate-skill-v3-to-v4.test.js +158 -0
- package/scripts/__tests__/test-export-parser-drift.js +149 -0
- package/scripts/__tests__/test-marketplace-export.js +114 -0
- package/scripts/__tests__/test-router-paths.js +82 -0
- package/scripts/__tests__/test-stability-promotion.js +244 -0
- package/scripts/__tests__/test-v3-1-alias-contract.js +109 -0
- package/scripts/__tests__/test-v3-1-skos-runtime.js +116 -0
- package/scripts/backfill-schema-version.js +198 -0
- package/scripts/build-field-reference.js +160 -0
- package/scripts/build-retrieval-baseline.js +511 -0
- package/scripts/check-markdown-links.js +211 -0
- package/scripts/check-protocol-consistency.js +979 -0
- package/scripts/export-marketplace-skills.js +610 -0
- package/scripts/export-skill.js +374 -0
- package/scripts/generate-manifest.js +787 -0
- package/scripts/lib/alias-contract.js +83 -0
- package/scripts/lib/audit-prompt-builder.js +771 -0
- package/scripts/lib/mock-grader.js +134 -0
- package/scripts/lib/parse-frontmatter.js +429 -0
- package/scripts/lib/roots.js +119 -0
- package/scripts/lint/check-archetype-sections.js +185 -0
- package/scripts/lint/check-category-enum.js +83 -0
- package/scripts/lint/check-routing-eval.js +146 -0
- package/scripts/lint/check-routing-quality.js +211 -0
- package/scripts/lint/check-stability-promotion.js +220 -0
- package/scripts/lint/format-code-frame.js +206 -0
- package/scripts/marketplace-install.js +125 -0
- package/scripts/migrate-category-to-enum.js +169 -0
- package/scripts/migrate-skill-v2-to-v3.js +424 -0
- package/scripts/migrate-skill-v3-to-v4.js +200 -0
- package/scripts/migrate-skill-v5-to-v6.js +304 -0
- package/scripts/restructure-by-category.js +85 -0
- package/scripts/seed-publication-classification.js +282 -0
- package/scripts/skill-audit.js +893 -0
- package/scripts/skill-graph-drift.js +483 -0
- package/scripts/skill-graph-route.js +766 -0
- package/scripts/skill-graph-routing-eval.js +393 -0
- package/scripts/skill-lint.js +1317 -0
- package/scripts/skill-overlap.js +213 -0
- package/scripts/verify-skill-md-export.js +201 -0
|
@@ -0,0 +1,893 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Skill Audit Runner
|
|
4
|
+
*
|
|
5
|
+
* Two modes:
|
|
6
|
+
*
|
|
7
|
+
* 1. Stub mode (default, no flags) — seeds findings.md / verdict.md / scorecard.md
|
|
8
|
+
* from lint output, leaving qualitative judgment as human TODOs. Fast, free,
|
|
9
|
+
* deterministic. This is the original behavior and is unchanged.
|
|
10
|
+
*
|
|
11
|
+
* 2. Graded mode (--graded) — on top of the stub, runs a prompt-driven
|
|
12
|
+
* seven-dimension review by calling an external model CLI for each
|
|
13
|
+
* dimension. Writes structured PASS / PASS WITH FIXES / FAIL verdicts with
|
|
14
|
+
* evidence quotes into findings.md / verdict.md / scorecard.md, replacing
|
|
15
|
+
* the human-TODO placeholders. Requires a grader CLI to be on PATH.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* node scripts/skill-audit.js <skill-name>
|
|
19
|
+
* node scripts/skill-audit.js <skill-name> --audit-root <path>
|
|
20
|
+
* node scripts/skill-audit.js <skill-name> --force
|
|
21
|
+
* node scripts/skill-audit.js <skill-name> --graded
|
|
22
|
+
* node scripts/skill-audit.js <skill-name> --graded --grader-cli "claude -p"
|
|
23
|
+
* node scripts/skill-audit.js <skill-name> --graded --grader-cli "codex exec"
|
|
24
|
+
*
|
|
25
|
+
* Flags:
|
|
26
|
+
* --audit-root <path> Output directory root (default: examples/audits/).
|
|
27
|
+
* --force Overwrite existing artifacts.
|
|
28
|
+
* --graded Enable the prompt-driven grader pass.
|
|
29
|
+
* --grader-cli <cmd> Shell command to invoke the grader. The prompt is
|
|
30
|
+
* piped to stdin; stdout is parsed. Default: `claude -p`.
|
|
31
|
+
* --grader-timeout <ms> Per-dimension timeout in milliseconds. Default: 120000.
|
|
32
|
+
*
|
|
33
|
+
* Produces under <audit-root>/<skill-name>/:
|
|
34
|
+
* findings.md — lint-derived findings + graded findings (graded mode) or human TODOs (stub)
|
|
35
|
+
* verdict.md — stub verdict (stub mode) or aggregated graded verdict (graded mode)
|
|
36
|
+
* scorecard.md — seven dimensions with scores from lint (metadata) and grader (others)
|
|
37
|
+
*
|
|
38
|
+
* Self-contained. Only uses Node built-ins. No external npm dependencies.
|
|
39
|
+
* Exit 0 on success, 1 on any error.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
'use strict';
|
|
43
|
+
|
|
44
|
+
const fs = require('fs');
|
|
45
|
+
const path = require('path');
|
|
46
|
+
const { spawnSync } = require('child_process');
|
|
47
|
+
const { workspaceRoot } = require('./lib/roots');
|
|
48
|
+
|
|
49
|
+
const REPO_ROOT = workspaceRoot();
|
|
50
|
+
const SKILLS_DIR = path.join(REPO_ROOT, 'skills');
|
|
51
|
+
|
|
52
|
+
const {
|
|
53
|
+
DIMENSIONS,
|
|
54
|
+
collectContext,
|
|
55
|
+
buildDimensionPrompt,
|
|
56
|
+
parseDimensionResponse,
|
|
57
|
+
aggregateVerdict,
|
|
58
|
+
} = require('./lib/audit-prompt-builder');
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// CLI argument parsing
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
const DEFAULT_GRADER_CLI = 'claude -p';
|
|
65
|
+
const DEFAULT_GRADER_TIMEOUT = 120_000;
|
|
66
|
+
|
|
67
|
+
function parseArgs(argv) {
|
|
68
|
+
const args = argv.slice(2);
|
|
69
|
+
const result = {
|
|
70
|
+
skillName: null,
|
|
71
|
+
auditRoot: path.join(REPO_ROOT, 'examples', 'audits'),
|
|
72
|
+
force: false,
|
|
73
|
+
graded: false,
|
|
74
|
+
graderCli: DEFAULT_GRADER_CLI,
|
|
75
|
+
graderTimeout: DEFAULT_GRADER_TIMEOUT,
|
|
76
|
+
errors: [],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
let i = 0;
|
|
80
|
+
while (i < args.length) {
|
|
81
|
+
const a = args[i];
|
|
82
|
+
if (a === '--audit-root') {
|
|
83
|
+
i++;
|
|
84
|
+
if (!args[i]) result.errors.push('--audit-root requires a path argument');
|
|
85
|
+
else result.auditRoot = path.resolve(args[i]);
|
|
86
|
+
} else if (a === '--force') {
|
|
87
|
+
result.force = true;
|
|
88
|
+
} else if (a === '--graded') {
|
|
89
|
+
result.graded = true;
|
|
90
|
+
} else if (a === '--grader-cli') {
|
|
91
|
+
i++;
|
|
92
|
+
if (!args[i]) result.errors.push('--grader-cli requires a command string');
|
|
93
|
+
else result.graderCli = args[i];
|
|
94
|
+
} else if (a === '--grader-timeout') {
|
|
95
|
+
i++;
|
|
96
|
+
const n = Number(args[i]);
|
|
97
|
+
if (!Number.isFinite(n) || n <= 0) result.errors.push('--grader-timeout requires a positive integer (ms)');
|
|
98
|
+
else result.graderTimeout = n;
|
|
99
|
+
} else if (!a.startsWith('--')) {
|
|
100
|
+
if (result.skillName) result.errors.push(`unexpected positional argument: ${a}`);
|
|
101
|
+
else result.skillName = a;
|
|
102
|
+
} else {
|
|
103
|
+
result.errors.push(`unknown flag: ${a}`);
|
|
104
|
+
}
|
|
105
|
+
i++;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!result.skillName) result.errors.push('missing required argument: <skill-name>');
|
|
109
|
+
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Lint runner
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
function runLint(skillDir) {
|
|
118
|
+
const lintScript = path.join(__dirname, 'skill-lint.js');
|
|
119
|
+
const result = spawnSync(
|
|
120
|
+
process.execPath,
|
|
121
|
+
[lintScript, skillDir, '--no-color', '--skip-generator-parity'],
|
|
122
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
123
|
+
);
|
|
124
|
+
return {
|
|
125
|
+
stdout: result.stdout || '',
|
|
126
|
+
stderr: result.stderr || '',
|
|
127
|
+
exitCode: result.status === null ? 1 : result.status,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Lint output parser
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @typedef {object} LintDiagnostic
|
|
137
|
+
* @property {'error'|'warn'} severity
|
|
138
|
+
* @property {string} filePath
|
|
139
|
+
* @property {number} line
|
|
140
|
+
* @property {number} column
|
|
141
|
+
* @property {string} message
|
|
142
|
+
* @property {string|null} help
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
function parseLintOutput(stderr) {
|
|
146
|
+
const diagnostics = [];
|
|
147
|
+
const lines = stderr.split('\n');
|
|
148
|
+
let current = null;
|
|
149
|
+
|
|
150
|
+
for (let i = 0; i < lines.length; i++) {
|
|
151
|
+
const line = lines[i];
|
|
152
|
+
|
|
153
|
+
const headerMatch = line.match(/^\[(error|warn)\]\s+(.+?):(\d+):(\d+)\s*$/);
|
|
154
|
+
if (headerMatch) {
|
|
155
|
+
if (current) diagnostics.push(current);
|
|
156
|
+
current = {
|
|
157
|
+
severity: headerMatch[1] === 'warn' ? 'warn' : 'error',
|
|
158
|
+
filePath: headerMatch[2],
|
|
159
|
+
line: parseInt(headerMatch[3], 10),
|
|
160
|
+
column: parseInt(headerMatch[4], 10),
|
|
161
|
+
message: '',
|
|
162
|
+
help: null,
|
|
163
|
+
};
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (current && current.message === '') {
|
|
168
|
+
const caretMatch = line.match(/^\s*\^\s+(.+)$/);
|
|
169
|
+
if (caretMatch) {
|
|
170
|
+
current.message = caretMatch[1].trim();
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (current && line.match(/^\s+help:\s+/)) {
|
|
176
|
+
current.help = line.replace(/^\s+help:\s+/, '').trim();
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (current) diagnostics.push(current);
|
|
182
|
+
return diagnostics.filter(d => d.message !== '');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Category inference from message text
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
function inferCategory(message) {
|
|
190
|
+
if (/missing required field|unknown field|enum|pattern|minLength|oneOf|sub-field/.test(message)) return 'Schema validity';
|
|
191
|
+
if (/parent directory|name/.test(message)) return 'Naming convention';
|
|
192
|
+
if (/relations\.|adjacent|related|broader|narrower|boundary|disjoint_with|verify_with|depends_on/.test(message)) return 'Relation quality';
|
|
193
|
+
if (/eval_artifacts|eval_state|routing_eval/.test(message)) return 'Eval quality';
|
|
194
|
+
if (/grounding|domain_object|truth_sources/.test(message)) return 'Grounding quality';
|
|
195
|
+
if (/section|Coverage|Philosophy|Verification|Do NOT/.test(message)) return 'Content quality';
|
|
196
|
+
if (/keywords|description|routing/.test(message)) return 'Activation quality';
|
|
197
|
+
if (/deprecated|migration|rename|v1/.test(message)) return 'Schema migration';
|
|
198
|
+
return 'Lint diagnostic';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function inferFix(d) {
|
|
202
|
+
if (d.help) return d.help;
|
|
203
|
+
if (/missing required field: (.+)/.test(d.message)) {
|
|
204
|
+
const field = d.message.match(/missing required field: (.+)/)[1];
|
|
205
|
+
return `Add the required \`${field}:\` field. See docs/field-reference.md for allowed values.`;
|
|
206
|
+
}
|
|
207
|
+
if (/unknown field: (.+)/.test(d.message)) {
|
|
208
|
+
const field = d.message.match(/unknown field: (.+)/)[1];
|
|
209
|
+
return `Remove or rename \`${field}\`. Check docs/field-reference.md for the canonical field list.`;
|
|
210
|
+
}
|
|
211
|
+
if (/not in enum/.test(d.message)) return 'Replace the value with one of the canonical enum values listed in docs/field-reference.md.';
|
|
212
|
+
if (/does not match any known skill/.test(d.message)) return 'Check the skill name for typos or remove the dangling relation target.';
|
|
213
|
+
if (/deprecated/.test(d.message)) return 'Follow the migration note in docs/manifest-field-mapping.md § Migration Note — v1 → v2.';
|
|
214
|
+
return 'Inspect the flagged line, correct the value, and re-run skill-lint.js.';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// Grader CLI invocation
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Invoke the grader CLI with the composed prompt on stdin, return stdout.
|
|
223
|
+
*
|
|
224
|
+
* The CLI command is user-supplied (--grader-cli) so we do not hardcode a
|
|
225
|
+
* provider. The prompt is piped to stdin to avoid shell-escaping issues with
|
|
226
|
+
* large multi-line prompts containing quotes and backticks.
|
|
227
|
+
*
|
|
228
|
+
* @param {string} graderCli Shell-style command, e.g. `claude -p` or `codex exec`.
|
|
229
|
+
* @param {string} prompt Full prompt text.
|
|
230
|
+
* @param {number} timeoutMs Per-call timeout.
|
|
231
|
+
* @returns {{ ok: boolean, stdout: string, stderr: string, exitCode: number, error: string|null }}
|
|
232
|
+
*/
|
|
233
|
+
function invokeGrader(graderCli, prompt, timeoutMs) {
|
|
234
|
+
const [cmd, ...cmdArgs] = graderCli.trim().split(/\s+/);
|
|
235
|
+
if (!cmd) {
|
|
236
|
+
return { ok: false, stdout: '', stderr: '', exitCode: 1, error: 'empty grader-cli' };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const result = spawnSync(cmd, cmdArgs, {
|
|
240
|
+
input: prompt,
|
|
241
|
+
encoding: 'utf8',
|
|
242
|
+
timeout: timeoutMs,
|
|
243
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
244
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (result.error) {
|
|
248
|
+
const msg = result.error.code === 'ENOENT'
|
|
249
|
+
? `grader CLI not found on PATH: ${cmd}. Pass --grader-cli to point at an installed CLI.`
|
|
250
|
+
: `grader CLI failed to spawn: ${result.error.message}`;
|
|
251
|
+
return { ok: false, stdout: '', stderr: '', exitCode: 1, error: msg };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (result.signal === 'SIGTERM' || result.status === null) {
|
|
255
|
+
return { ok: false, stdout: result.stdout || '', stderr: result.stderr || '', exitCode: 124, error: `grader CLI timed out after ${timeoutMs}ms` };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
ok: result.status === 0,
|
|
260
|
+
stdout: result.stdout || '',
|
|
261
|
+
stderr: result.stderr || '',
|
|
262
|
+
exitCode: result.status,
|
|
263
|
+
error: result.status === 0 ? null : `grader CLI exited with status ${result.status}: ${(result.stderr || '').trim().slice(0, 500)}`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// Graded pass — run every dimension, collect verdicts
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
function runGradedPass(skillDir, opts) {
|
|
272
|
+
const context = collectContext({ skillDir, repoRoot: REPO_ROOT });
|
|
273
|
+
const results = [];
|
|
274
|
+
|
|
275
|
+
for (const dimension of DIMENSIONS) {
|
|
276
|
+
const applies = dimension.appliesWhen(context.frontmatter);
|
|
277
|
+
if (!applies) {
|
|
278
|
+
results.push({
|
|
279
|
+
dimension,
|
|
280
|
+
skipped: true,
|
|
281
|
+
verdict: {
|
|
282
|
+
dimension: dimension.id,
|
|
283
|
+
score: 'N/A',
|
|
284
|
+
verdict: 'N/A',
|
|
285
|
+
justification: `Dimension does not apply to this skill (scope: ${context.frontmatter && context.frontmatter.scope}).`,
|
|
286
|
+
findings: [],
|
|
287
|
+
raw: '',
|
|
288
|
+
},
|
|
289
|
+
error: null,
|
|
290
|
+
});
|
|
291
|
+
console.log(` ${dimension.id}: N/A (skipped — does not apply)`);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const prompt = buildDimensionPrompt({ dimension, context });
|
|
296
|
+
process.stdout.write(` ${dimension.id}: calling grader (${prompt.length} char prompt)… `);
|
|
297
|
+
const call = invokeGrader(opts.graderCli, prompt, opts.graderTimeout);
|
|
298
|
+
|
|
299
|
+
if (!call.ok) {
|
|
300
|
+
console.log(`ERROR — ${call.error}`);
|
|
301
|
+
results.push({ dimension, skipped: false, verdict: null, error: call.error });
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const parsed = parseDimensionResponse(call.stdout, dimension);
|
|
306
|
+
if (!parsed.ok) {
|
|
307
|
+
const preview = call.stdout.trim().slice(0, 200).replace(/\n/g, ' ');
|
|
308
|
+
console.log(`PARSE ERROR — ${parsed.error} (preview: ${preview})`);
|
|
309
|
+
results.push({ dimension, skipped: false, verdict: null, error: parsed.error, raw: call.stdout });
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
console.log(`${parsed.verdict.verdict} — score ${parsed.verdict.score} — ${parsed.verdict.findings.length} finding(s)`);
|
|
314
|
+
results.push({ dimension, skipped: false, verdict: parsed.verdict, error: null });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return { context, results };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// Artifact template builders — stub mode
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
|
|
324
|
+
function buildFindingsStub(skillName, diagnostics, isoDate) {
|
|
325
|
+
const errors = diagnostics.filter(d => d.severity === 'error');
|
|
326
|
+
const warnings = diagnostics.filter(d => d.severity === 'warn');
|
|
327
|
+
|
|
328
|
+
const findingBlocks = [];
|
|
329
|
+
let fIndex = 1;
|
|
330
|
+
|
|
331
|
+
for (const d of diagnostics) {
|
|
332
|
+
const severity = d.severity === 'error' ? 'P1' : 'P2';
|
|
333
|
+
const category = inferCategory(d.message);
|
|
334
|
+
const fix = inferFix(d);
|
|
335
|
+
findingBlocks.push([
|
|
336
|
+
`ID: F${fIndex}`,
|
|
337
|
+
`Severity: ${severity}`,
|
|
338
|
+
`Surface: ${d.filePath}:${d.line}:${d.column}`,
|
|
339
|
+
`Category: ${category}`,
|
|
340
|
+
`Problem: ${d.message}`,
|
|
341
|
+
`Evidence: Emitted by skill-lint.js — see ${d.filePath} line ${d.line}`,
|
|
342
|
+
`Required action: ${fix}`,
|
|
343
|
+
].join('\n'));
|
|
344
|
+
fIndex++;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const humanJudgmentTodos = [
|
|
348
|
+
{ surface: 'activation', title: 'Activation quality — routing coverage', note: 'Does the description name real trigger scenarios? Are keywords specific and not generic filler? Does the skill under-trigger or over-trigger for its intended use case?' },
|
|
349
|
+
{ surface: 'relations', title: 'Relation quality — graph correctness', note: 'Do relations point at semantically correct neighbors? Are boundary handoffs crisp enough to prevent misuse? Are broader/narrower claims taxonomic rather than associative? Are dependencies real?' },
|
|
350
|
+
{ surface: 'grounding', title: 'Grounding quality — claims vs truth sources', note: 'If scope: codebase, do all truth_sources exist? Do claims in the body match the referenced files? Classify any mismatch as skill drift, code drift, or doc drift.' },
|
|
351
|
+
{ surface: 'content', title: 'Content quality — completeness and density', note: 'Does the skill have a clear Coverage section, a Philosophy section, at least one decision table or checklist, and explicit negative bounds (Do NOT Use When)? Does it contain generic filler that adds no routing signal?' },
|
|
352
|
+
{ surface: 'evals', title: 'Eval quality — coverage and realism', note: 'Do eval files exist if the skill is expected to be graded? Do they test realistic prompts — not trivia — and cover boundaries and failure cases as well as the happy path?' },
|
|
353
|
+
];
|
|
354
|
+
|
|
355
|
+
for (const todo of humanJudgmentTodos) {
|
|
356
|
+
findingBlocks.push([
|
|
357
|
+
`ID: F${fIndex}`,
|
|
358
|
+
`Severity: TODO`,
|
|
359
|
+
`Surface: ${todo.surface}`,
|
|
360
|
+
`Category: ${todo.title}`,
|
|
361
|
+
`Problem: TODO — human judgment required`,
|
|
362
|
+
`Evidence: TODO — reviewer must inspect the skill body`,
|
|
363
|
+
`Required action: ${todo.note}`,
|
|
364
|
+
].join('\n'));
|
|
365
|
+
fIndex++;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
let requiredFixes = '';
|
|
369
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
370
|
+
requiredFixes = 'None identified by lint. See human-judgment finding blocks above for remaining review areas.';
|
|
371
|
+
} else {
|
|
372
|
+
const fixLines = [];
|
|
373
|
+
let fi = 1;
|
|
374
|
+
for (const d of diagnostics) {
|
|
375
|
+
const sev = d.severity === 'error' ? '[P1 error]' : '[P2 warning]';
|
|
376
|
+
fixLines.push(`- F${fi} ${sev}: ${d.message}`);
|
|
377
|
+
fi++;
|
|
378
|
+
}
|
|
379
|
+
requiredFixes = fixLines.join('\n');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return [
|
|
383
|
+
'# Findings',
|
|
384
|
+
'',
|
|
385
|
+
'## Skill',
|
|
386
|
+
'',
|
|
387
|
+
`\`${skillName}\``,
|
|
388
|
+
'',
|
|
389
|
+
'## Audit Date',
|
|
390
|
+
'',
|
|
391
|
+
isoDate,
|
|
392
|
+
'',
|
|
393
|
+
'## Verdict Summary',
|
|
394
|
+
'',
|
|
395
|
+
verdictLabelFromLint(diagnostics),
|
|
396
|
+
'',
|
|
397
|
+
'## Findings',
|
|
398
|
+
'',
|
|
399
|
+
findingBlocks.join('\n\n'),
|
|
400
|
+
'',
|
|
401
|
+
'## Required Fixes',
|
|
402
|
+
'',
|
|
403
|
+
requiredFixes,
|
|
404
|
+
'',
|
|
405
|
+
].join('\n');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function buildVerdictStub(skillName, diagnostics, isoDate) {
|
|
409
|
+
const errors = diagnostics.filter(d => d.severity === 'error');
|
|
410
|
+
const warnings = diagnostics.filter(d => d.severity === 'warn');
|
|
411
|
+
const verdict = verdictLabelFromLint(diagnostics);
|
|
412
|
+
|
|
413
|
+
let rationale = '';
|
|
414
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
415
|
+
rationale = [
|
|
416
|
+
'The skill passes all deterministic lint checks (schema validity, naming convention, relation target existence, eval coherence).',
|
|
417
|
+
'Human judgment is required to assess activation quality, relation semantics, grounding fidelity, content quality, eval realism, and portability.',
|
|
418
|
+
'Update this verdict after completing the qualitative review sections in findings.md.',
|
|
419
|
+
].join(' ');
|
|
420
|
+
} else if (errors.length === 0) {
|
|
421
|
+
const warnList = warnings.map(w => `- ${w.message}`).join('\n');
|
|
422
|
+
rationale = [
|
|
423
|
+
`The skill passes schema validation with ${warnings.length} warning(s) emitted by skill-lint.js:`,
|
|
424
|
+
'',
|
|
425
|
+
warnList,
|
|
426
|
+
'',
|
|
427
|
+
'Human judgment is required for the qualitative dimensions (activation, content, evals, portability). Update this verdict after completing findings.md.',
|
|
428
|
+
].join('\n');
|
|
429
|
+
} else {
|
|
430
|
+
const errList = errors.map(e => `- ${e.message}`).join('\n');
|
|
431
|
+
rationale = [
|
|
432
|
+
`The skill fails deterministic lint with ${errors.length} error(s):`,
|
|
433
|
+
'',
|
|
434
|
+
errList,
|
|
435
|
+
'',
|
|
436
|
+
'All lint errors must be resolved before qualitative review is meaningful. Re-run `node scripts/skill-lint.js skills/<skill-name>` after each fix.',
|
|
437
|
+
].join('\n');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return [
|
|
441
|
+
'# Verdict',
|
|
442
|
+
'',
|
|
443
|
+
'## Skill',
|
|
444
|
+
'',
|
|
445
|
+
`\`${skillName}\``,
|
|
446
|
+
'',
|
|
447
|
+
'## Audit Date',
|
|
448
|
+
'',
|
|
449
|
+
isoDate,
|
|
450
|
+
'',
|
|
451
|
+
'## Final Verdict',
|
|
452
|
+
'',
|
|
453
|
+
verdict,
|
|
454
|
+
'',
|
|
455
|
+
'## Rationale',
|
|
456
|
+
'',
|
|
457
|
+
rationale,
|
|
458
|
+
'',
|
|
459
|
+
'## Human Judgment Required',
|
|
460
|
+
'',
|
|
461
|
+
'This is a stub verdict generated by `node scripts/skill-audit.js`. It reflects only the deterministic lint result.',
|
|
462
|
+
'A human auditor must review the following before this verdict is final:',
|
|
463
|
+
'',
|
|
464
|
+
'- Activation quality (routing coverage, keyword specificity)',
|
|
465
|
+
'- Relation semantics (related/broader/boundary correctness)',
|
|
466
|
+
'- Grounding fidelity (claims vs truth sources, when scope: codebase)',
|
|
467
|
+
'- Content quality (decision tables, Philosophy section, negative bounds)',
|
|
468
|
+
'- Eval quality (coverage, realism, boundary cases)',
|
|
469
|
+
'- Portability (no private assumptions leak through, export targets are real)',
|
|
470
|
+
'',
|
|
471
|
+
'## Follow-up State',
|
|
472
|
+
'',
|
|
473
|
+
'TODO — set to one of: `No fixes required`, `Fixes applied`, `Fixes deferred — <reason>`, or `Pending human review`.',
|
|
474
|
+
'',
|
|
475
|
+
].join('\n');
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function buildScorecardStub(skillName, diagnostics, scope) {
|
|
479
|
+
const errors = diagnostics.filter(d => d.severity === 'error');
|
|
480
|
+
const warnings = diagnostics.filter(d => d.severity === 'warning');
|
|
481
|
+
const schemaErrs = errors.filter(d => {
|
|
482
|
+
const cat = inferCategory(d.message);
|
|
483
|
+
return cat === 'Schema validity' || cat === 'Naming convention';
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// v0.5.0: 3-column markdown (Dimension | Score | Note) + 1–5 numeric scores
|
|
487
|
+
// aligned with `evaluation` doctrine. Prior version emitted a 2-column row
|
|
488
|
+
// for Metadata validity which broke markdown rendering.
|
|
489
|
+
let metaScore, metaNote;
|
|
490
|
+
if (schemaErrs.length > 0) {
|
|
491
|
+
metaScore = '1';
|
|
492
|
+
metaNote = `auto: ${schemaErrs.length} lint error(s) — schema contract violated`;
|
|
493
|
+
} else if (warnings.length > 0) {
|
|
494
|
+
metaScore = '4';
|
|
495
|
+
metaNote = `auto: lint passes with ${warnings.length} warning(s)`;
|
|
496
|
+
} else {
|
|
497
|
+
metaScore = '5';
|
|
498
|
+
metaNote = 'auto: lint passes clean';
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const groundingRow = scope === 'portable'
|
|
502
|
+
? '| Grounding fidelity | N/A | `scope: portable` — grounding dimension does not apply |'
|
|
503
|
+
: '| Grounding fidelity | TODO | Human review required — verify claims match truth sources |';
|
|
504
|
+
|
|
505
|
+
const rows = [
|
|
506
|
+
`| Metadata validity | ${metaScore} | ${metaNote} |`,
|
|
507
|
+
'| Activation quality | TODO | Human review required — see findings.md |',
|
|
508
|
+
'| Relation quality | TODO | Human review required — see findings.md |',
|
|
509
|
+
groundingRow,
|
|
510
|
+
'| Content quality | TODO | Human review required — see findings.md |',
|
|
511
|
+
'| Eval quality | TODO | Human review required — see findings.md |',
|
|
512
|
+
'| Portability quality | TODO | Human review required — see findings.md |',
|
|
513
|
+
];
|
|
514
|
+
|
|
515
|
+
return [
|
|
516
|
+
'# Scorecard',
|
|
517
|
+
'',
|
|
518
|
+
'## Skill',
|
|
519
|
+
'',
|
|
520
|
+
`\`${skillName}\``,
|
|
521
|
+
'',
|
|
522
|
+
'## Dimensions',
|
|
523
|
+
'',
|
|
524
|
+
'| Dimension | Score | Note |',
|
|
525
|
+
'|---|---|---|',
|
|
526
|
+
...rows,
|
|
527
|
+
'',
|
|
528
|
+
'> **Note:** This scorecard was generated by `node scripts/skill-audit.js`.',
|
|
529
|
+
'> Schema validity is auto-scored from `skill-lint.js` output.',
|
|
530
|
+
'> All other dimensions require human judgment. Replace each TODO with a',
|
|
531
|
+
'> 1–5 score and a short justification once the review is complete.',
|
|
532
|
+
'',
|
|
533
|
+
].join('\n');
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// ---------------------------------------------------------------------------
|
|
537
|
+
// Artifact template builders — graded mode
|
|
538
|
+
// ---------------------------------------------------------------------------
|
|
539
|
+
|
|
540
|
+
function buildFindingsGraded(skillName, diagnostics, isoDate, gradedResults, finalVerdict, graderCli) {
|
|
541
|
+
const findingBlocks = [];
|
|
542
|
+
let fIndex = 1;
|
|
543
|
+
|
|
544
|
+
// Lint-derived findings first, unchanged.
|
|
545
|
+
for (const d of diagnostics) {
|
|
546
|
+
const severity = d.severity === 'error' ? 'P1' : 'P2';
|
|
547
|
+
const category = inferCategory(d.message);
|
|
548
|
+
const fix = inferFix(d);
|
|
549
|
+
findingBlocks.push([
|
|
550
|
+
`ID: F${fIndex}`,
|
|
551
|
+
`Severity: ${severity}`,
|
|
552
|
+
`Surface: ${d.filePath}:${d.line}:${d.column}`,
|
|
553
|
+
`Category: ${category}`,
|
|
554
|
+
`Source: skill-lint.js`,
|
|
555
|
+
`Problem: ${d.message}`,
|
|
556
|
+
`Evidence: Emitted by skill-lint.js — see ${d.filePath} line ${d.line}`,
|
|
557
|
+
`Required action: ${fix}`,
|
|
558
|
+
].join('\n'));
|
|
559
|
+
fIndex++;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Graded findings, one block per finding, tagged with dimension + grader.
|
|
563
|
+
for (const r of gradedResults) {
|
|
564
|
+
if (r.error || !r.verdict || r.verdict.verdict === 'N/A') continue;
|
|
565
|
+
for (const finding of r.verdict.findings) {
|
|
566
|
+
findingBlocks.push([
|
|
567
|
+
`ID: F${fIndex}`,
|
|
568
|
+
`Severity: ${finding.severity}`,
|
|
569
|
+
`Surface: ${finding.surface}`,
|
|
570
|
+
`Category: ${r.dimension.label}`,
|
|
571
|
+
`Source: grader (${graderCli})`,
|
|
572
|
+
`Problem: ${finding.problem}`,
|
|
573
|
+
`Evidence: ${finding.evidence}`,
|
|
574
|
+
`Required action: ${finding.required_action}`,
|
|
575
|
+
].join('\n'));
|
|
576
|
+
fIndex++;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Graded errors surface as findings too so they don't get lost.
|
|
581
|
+
for (const r of gradedResults) {
|
|
582
|
+
if (!r.error) continue;
|
|
583
|
+
findingBlocks.push([
|
|
584
|
+
`ID: F${fIndex}`,
|
|
585
|
+
`Severity: P2`,
|
|
586
|
+
`Surface: grader-${r.dimension.id}`,
|
|
587
|
+
`Category: Grader infrastructure`,
|
|
588
|
+
`Source: skill-audit.js`,
|
|
589
|
+
`Problem: Grader call failed for dimension "${r.dimension.label}"`,
|
|
590
|
+
`Evidence: ${r.error.slice(0, 400)}`,
|
|
591
|
+
`Required action: Re-run with a working grader CLI (see --grader-cli) or complete this dimension manually.`,
|
|
592
|
+
].join('\n'));
|
|
593
|
+
fIndex++;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const requiredFixesLines = [];
|
|
597
|
+
if (diagnostics.length > 0) {
|
|
598
|
+
let fi = 1;
|
|
599
|
+
for (const d of diagnostics) {
|
|
600
|
+
const sev = d.severity === 'error' ? '[P1 error]' : '[P2 warning]';
|
|
601
|
+
requiredFixesLines.push(`- F${fi} ${sev}: ${d.message}`);
|
|
602
|
+
fi++;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
for (const r of gradedResults) {
|
|
606
|
+
if (r.error || !r.verdict || r.verdict.verdict === 'PASS' || r.verdict.verdict === 'N/A') continue;
|
|
607
|
+
const count = r.verdict.findings.length;
|
|
608
|
+
if (count > 0) {
|
|
609
|
+
requiredFixesLines.push(`- ${r.dimension.label}: ${r.verdict.verdict} — ${count} finding(s) from grader`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const requiredFixes = requiredFixesLines.length === 0
|
|
613
|
+
? 'None — all dimensions PASS under grader review and lint is clean.'
|
|
614
|
+
: requiredFixesLines.join('\n');
|
|
615
|
+
|
|
616
|
+
return [
|
|
617
|
+
'# Findings',
|
|
618
|
+
'',
|
|
619
|
+
'## Skill',
|
|
620
|
+
'',
|
|
621
|
+
`\`${skillName}\``,
|
|
622
|
+
'',
|
|
623
|
+
'## Audit Date',
|
|
624
|
+
'',
|
|
625
|
+
isoDate,
|
|
626
|
+
'',
|
|
627
|
+
'## Audit Mode',
|
|
628
|
+
'',
|
|
629
|
+
`\`--graded\` (grader: \`${graderCli}\`)`,
|
|
630
|
+
'',
|
|
631
|
+
'## Verdict Summary',
|
|
632
|
+
'',
|
|
633
|
+
finalVerdict,
|
|
634
|
+
'',
|
|
635
|
+
'## Findings',
|
|
636
|
+
'',
|
|
637
|
+
findingBlocks.length === 0 ? '_(no findings — lint clean and grader PASS across every dimension)_' : findingBlocks.join('\n\n'),
|
|
638
|
+
'',
|
|
639
|
+
'## Required Fixes',
|
|
640
|
+
'',
|
|
641
|
+
requiredFixes,
|
|
642
|
+
'',
|
|
643
|
+
].join('\n');
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function buildVerdictGraded(skillName, isoDate, gradedResults, finalVerdict, graderCli) {
|
|
647
|
+
const summaryRows = [
|
|
648
|
+
'| Dimension | Verdict | Score |',
|
|
649
|
+
'|---|---|---|',
|
|
650
|
+
...gradedResults.map(r => {
|
|
651
|
+
if (r.error) return `| ${r.dimension.label} | ERROR | n/a |`;
|
|
652
|
+
const v = r.verdict;
|
|
653
|
+
return `| ${r.dimension.label} | ${v.verdict} | ${v.score} |`;
|
|
654
|
+
}),
|
|
655
|
+
].join('\n');
|
|
656
|
+
|
|
657
|
+
const justifications = gradedResults
|
|
658
|
+
.filter(r => r.verdict && !r.error)
|
|
659
|
+
.map(r => `- **${r.dimension.label}** (${r.verdict.verdict}, score ${r.verdict.score}): ${r.verdict.justification}`)
|
|
660
|
+
.join('\n');
|
|
661
|
+
|
|
662
|
+
const errorNotes = gradedResults
|
|
663
|
+
.filter(r => r.error)
|
|
664
|
+
.map(r => `- **${r.dimension.label}** — grader error: ${r.error.slice(0, 300)}`)
|
|
665
|
+
.join('\n');
|
|
666
|
+
|
|
667
|
+
return [
|
|
668
|
+
'# Verdict',
|
|
669
|
+
'',
|
|
670
|
+
'## Skill',
|
|
671
|
+
'',
|
|
672
|
+
`\`${skillName}\``,
|
|
673
|
+
'',
|
|
674
|
+
'## Audit Date',
|
|
675
|
+
'',
|
|
676
|
+
isoDate,
|
|
677
|
+
'',
|
|
678
|
+
'## Audit Mode',
|
|
679
|
+
'',
|
|
680
|
+
`\`--graded\` (grader: \`${graderCli}\`)`,
|
|
681
|
+
'',
|
|
682
|
+
'## Final Verdict',
|
|
683
|
+
'',
|
|
684
|
+
finalVerdict,
|
|
685
|
+
'',
|
|
686
|
+
'## Dimension Summary',
|
|
687
|
+
'',
|
|
688
|
+
summaryRows,
|
|
689
|
+
'',
|
|
690
|
+
'## Rationale',
|
|
691
|
+
'',
|
|
692
|
+
justifications || '_(no per-dimension justifications — every dimension errored)_',
|
|
693
|
+
'',
|
|
694
|
+
errorNotes ? ['## Grader Errors', '', errorNotes, ''].join('\n') : '',
|
|
695
|
+
'## Follow-up State',
|
|
696
|
+
'',
|
|
697
|
+
'TODO — set to one of: `No fixes required`, `Fixes applied`, `Fixes deferred — <reason>`, or `Pending human review`.',
|
|
698
|
+
'',
|
|
699
|
+
].join('\n');
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function buildScorecardGraded(skillName, diagnostics, gradedResults) {
|
|
703
|
+
const errors = diagnostics.filter(d => d.severity === 'error');
|
|
704
|
+
const warnings = diagnostics.filter(d => d.severity === 'warning');
|
|
705
|
+
const schemaErrs = errors.filter(d => {
|
|
706
|
+
const cat = inferCategory(d.message);
|
|
707
|
+
return cat === 'Schema validity' || cat === 'Naming convention';
|
|
708
|
+
});
|
|
709
|
+
// v0.5.0: use the full 1–5 scale per `skills/evaluation/SKILL.md:69-106`
|
|
710
|
+
// doctrine. 5 = zero lint errors and zero warnings. 4 = zero errors, some
|
|
711
|
+
// warnings. 1 = lint errors present (the skill fails the schema contract).
|
|
712
|
+
let metaScore, metaNote;
|
|
713
|
+
if (schemaErrs.length > 0) {
|
|
714
|
+
metaScore = '1';
|
|
715
|
+
metaNote = `${schemaErrs.length} lint error(s) — schema contract violated`;
|
|
716
|
+
} else if (warnings.length > 0) {
|
|
717
|
+
metaScore = '4';
|
|
718
|
+
metaNote = `lint passes with ${warnings.length} warning(s)`;
|
|
719
|
+
} else {
|
|
720
|
+
metaScore = '5';
|
|
721
|
+
metaNote = 'lint passes clean';
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Metadata validity is lint-derived; all other rows come from the grader.
|
|
725
|
+
const byId = new Map(gradedResults.map(r => [r.dimension.id, r]));
|
|
726
|
+
|
|
727
|
+
function rowFor(id, label) {
|
|
728
|
+
if (id === 'metadata') {
|
|
729
|
+
return `| ${label} | ${metaScore} | auto: ${metaNote} |`;
|
|
730
|
+
}
|
|
731
|
+
const r = byId.get(id);
|
|
732
|
+
if (!r) return `| ${label} | TODO | (no grader output) |`;
|
|
733
|
+
if (r.error) return `| ${label} | ERROR | grader error — see findings.md |`;
|
|
734
|
+
const v = r.verdict;
|
|
735
|
+
const note = (v.justification || '').replace(/\|/g, '\\|').slice(0, 300);
|
|
736
|
+
return `| ${label} | ${v.score} | ${v.verdict} — ${note} |`;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const rows = [
|
|
740
|
+
rowFor('metadata', 'Metadata validity'),
|
|
741
|
+
rowFor('activation', 'Activation quality'),
|
|
742
|
+
rowFor('relation', 'Relation quality'),
|
|
743
|
+
rowFor('grounding', 'Grounding fidelity'),
|
|
744
|
+
rowFor('content', 'Content quality'),
|
|
745
|
+
rowFor('eval', 'Eval quality'),
|
|
746
|
+
rowFor('portability', 'Portability quality'),
|
|
747
|
+
];
|
|
748
|
+
|
|
749
|
+
return [
|
|
750
|
+
'# Scorecard',
|
|
751
|
+
'',
|
|
752
|
+
'## Skill',
|
|
753
|
+
'',
|
|
754
|
+
`\`${skillName}\``,
|
|
755
|
+
'',
|
|
756
|
+
'## Dimensions',
|
|
757
|
+
'',
|
|
758
|
+
'| Dimension | Score | Note |',
|
|
759
|
+
'|---|---|---|',
|
|
760
|
+
...rows,
|
|
761
|
+
'',
|
|
762
|
+
'> **Note:** Metadata validity is auto-scored from `skill-lint.js`. All other',
|
|
763
|
+
'> dimensions come from the `--graded` grader pass. See `verdict.md` for the',
|
|
764
|
+
'> per-dimension rationale and `findings.md` for the specific finding evidence.',
|
|
765
|
+
'',
|
|
766
|
+
].join('\n');
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// ---------------------------------------------------------------------------
|
|
770
|
+
// Verdict label helper (stub mode)
|
|
771
|
+
// ---------------------------------------------------------------------------
|
|
772
|
+
|
|
773
|
+
function verdictLabelFromLint(diagnostics) {
|
|
774
|
+
const errors = diagnostics.filter(d => d.severity === 'error');
|
|
775
|
+
if (errors.length > 0) return 'FAIL';
|
|
776
|
+
// v0.5.0: stub mode completes only the Metadata-validity row (from lint).
|
|
777
|
+
// The other six dimensions are human-judgment TODOs. Per `skills/evaluation/
|
|
778
|
+
// SKILL.md:69-106` doctrine, a skill is not Done until all scores >= 4 —
|
|
779
|
+
// so a stub with 6 TODO rows cannot honestly claim PASS WITH FIXES. PARTIAL
|
|
780
|
+
// reflects "useful but still incomplete" (the vocabulary definition in
|
|
781
|
+
// `SKILL_AUDIT_CHECKLIST.md § Verdict`).
|
|
782
|
+
return 'PARTIAL';
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// ---------------------------------------------------------------------------
|
|
786
|
+
// Scope reader (fallback for when we don't collect full context)
|
|
787
|
+
// ---------------------------------------------------------------------------
|
|
788
|
+
|
|
789
|
+
function readScopeFromSkill(skillFilePath) {
|
|
790
|
+
try {
|
|
791
|
+
const { parseFrontmatter } = require('./lib/parse-frontmatter');
|
|
792
|
+
const text = fs.readFileSync(skillFilePath, 'utf8');
|
|
793
|
+
const fm = parseFrontmatter(text);
|
|
794
|
+
return (fm && fm.scope) ? String(fm.scope) : null;
|
|
795
|
+
} catch (_) {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// ---------------------------------------------------------------------------
|
|
801
|
+
// Main
|
|
802
|
+
// ---------------------------------------------------------------------------
|
|
803
|
+
|
|
804
|
+
function main() {
|
|
805
|
+
const opts = parseArgs(process.argv);
|
|
806
|
+
|
|
807
|
+
if (opts.errors.length > 0) {
|
|
808
|
+
for (const e of opts.errors) console.error(`error: ${e}`);
|
|
809
|
+
console.error('\nUsage: node scripts/skill-audit.js <skill-name> [--audit-root <path>] [--force] [--graded [--grader-cli <cmd>] [--grader-timeout <ms>]]');
|
|
810
|
+
process.exit(1);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const skillDir = path.join(SKILLS_DIR, opts.skillName);
|
|
814
|
+
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
815
|
+
if (!fs.existsSync(skillDir)) {
|
|
816
|
+
console.error(`error: skill directory not found: ${skillDir}`);
|
|
817
|
+
console.error(` Available skills: ${fs.readdirSync(SKILLS_DIR).join(', ')}`);
|
|
818
|
+
process.exit(1);
|
|
819
|
+
}
|
|
820
|
+
if (!fs.existsSync(skillFile)) {
|
|
821
|
+
console.error(`error: SKILL.md not found in ${skillDir}`);
|
|
822
|
+
process.exit(1);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const outDir = path.join(opts.auditRoot, opts.skillName);
|
|
826
|
+
const targetFiles = ['findings.md', 'verdict.md', 'scorecard.md'];
|
|
827
|
+
if (!opts.force) {
|
|
828
|
+
const existing = targetFiles.filter(f => fs.existsSync(path.join(outDir, f)));
|
|
829
|
+
if (existing.length > 0) {
|
|
830
|
+
console.error(`error: audit artifacts already exist in ${outDir}`);
|
|
831
|
+
console.error(` Existing files: ${existing.join(', ')}`);
|
|
832
|
+
console.error(' Pass --force to overwrite.');
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
console.log(`Running skill-lint.js on skills/${opts.skillName}…`);
|
|
838
|
+
const { stderr, exitCode } = runLint(skillDir);
|
|
839
|
+
const diagnostics = parseLintOutput(stderr);
|
|
840
|
+
const lintErrors = diagnostics.filter(d => d.severity === 'error');
|
|
841
|
+
const lintWarnings = diagnostics.filter(d => d.severity === 'warn');
|
|
842
|
+
if (exitCode === 0) {
|
|
843
|
+
console.log(` lint: PASS${lintWarnings.length > 0 ? ` (${lintWarnings.length} warning(s))` : ''}`);
|
|
844
|
+
} else {
|
|
845
|
+
console.log(` lint: FAIL (${lintErrors.length} error(s), ${lintWarnings.length} warning(s))`);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const isoDate = new Date().toISOString().slice(0, 10);
|
|
849
|
+
|
|
850
|
+
let findingsMd, verdictMd, scorecardMd, summaryTail;
|
|
851
|
+
|
|
852
|
+
if (!opts.graded) {
|
|
853
|
+
// ---- stub path (unchanged) ----
|
|
854
|
+
const scope = readScopeFromSkill(skillFile);
|
|
855
|
+
findingsMd = buildFindingsStub(opts.skillName, diagnostics, isoDate);
|
|
856
|
+
verdictMd = buildVerdictStub(opts.skillName, diagnostics, isoDate);
|
|
857
|
+
scorecardMd = buildScorecardStub(opts.skillName, diagnostics, scope);
|
|
858
|
+
const humanTodoCount = 5;
|
|
859
|
+
summaryTail = `${diagnostics.length} lint finding(s) stubbed, ${humanTodoCount} human-judgment TODOs.`;
|
|
860
|
+
} else {
|
|
861
|
+
// ---- graded path ----
|
|
862
|
+
console.log(`\nRunning graded pass — grader: \`${opts.graderCli}\` (timeout ${opts.graderTimeout}ms/call)`);
|
|
863
|
+
const { results } = runGradedPass(skillDir, opts);
|
|
864
|
+
|
|
865
|
+
const validVerdicts = results.filter(r => r.verdict && !r.error).map(r => r.verdict);
|
|
866
|
+
const finalVerdict = lintErrors.length > 0 ? 'FAIL' : aggregateVerdict(validVerdicts);
|
|
867
|
+
|
|
868
|
+
findingsMd = buildFindingsGraded(opts.skillName, diagnostics, isoDate, results, finalVerdict, opts.graderCli);
|
|
869
|
+
verdictMd = buildVerdictGraded(opts.skillName, isoDate, results, finalVerdict, opts.graderCli);
|
|
870
|
+
scorecardMd = buildScorecardGraded(opts.skillName, diagnostics, results);
|
|
871
|
+
|
|
872
|
+
const graderErrors = results.filter(r => r.error).length;
|
|
873
|
+
const gradedFindingCount = results.reduce((n, r) => n + (r.verdict ? r.verdict.findings.length : 0), 0);
|
|
874
|
+
summaryTail = `${diagnostics.length} lint finding(s), ${gradedFindingCount} graded finding(s), ${graderErrors} grader error(s).`;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
878
|
+
fs.writeFileSync(path.join(outDir, 'findings.md'), findingsMd, 'utf8');
|
|
879
|
+
fs.writeFileSync(path.join(outDir, 'verdict.md'), verdictMd, 'utf8');
|
|
880
|
+
fs.writeFileSync(path.join(outDir, 'scorecard.md'), scorecardMd, 'utf8');
|
|
881
|
+
|
|
882
|
+
console.log(`\nWrote ${path.relative(REPO_ROOT, outDir)}/{findings,verdict,scorecard}.md — ${summaryTail}`);
|
|
883
|
+
|
|
884
|
+
if (lintErrors.length > 0) {
|
|
885
|
+
console.log('\nNext step: fix the lint errors listed in findings.md, then re-run skill-audit.js --force.');
|
|
886
|
+
} else if (!opts.graded) {
|
|
887
|
+
console.log('\nNext step: open findings.md and complete the human-judgment TODO sections, or re-run with --graded to invoke the grader.');
|
|
888
|
+
} else {
|
|
889
|
+
console.log('\nNext step: address the graded findings in findings.md, then re-run with --force to refresh the verdict.');
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
main();
|