@vegastack/vegafactory 0.19.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.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +417 -0
  3. package/dist/dashboard-artifact.json +21240 -0
  4. package/dist/index.js +22752 -0
  5. package/dist/run-wrapper.js +377 -0
  6. package/package.json +46 -0
  7. package/skill/dev-architect/SKILL.md +106 -0
  8. package/skill/dev-architect/agents/openai.yaml +4 -0
  9. package/skill/dev-architect/references/ai-agents.md +96 -0
  10. package/skill/dev-architect/references/conventions.md +113 -0
  11. package/skill/dev-architect/references/data.md +89 -0
  12. package/skill/dev-architect/references/infra.md +99 -0
  13. package/skill/dev-architect/references/mobile.md +75 -0
  14. package/skill/dev-architect/references/pinned-facts.md +133 -0
  15. package/skill/dev-architect/references/principles.md +117 -0
  16. package/skill/dev-architect/references/security.md +90 -0
  17. package/skill/dev-architect/references/stack.md +38 -0
  18. package/skill/dev-architect/references/web.md +102 -0
  19. package/skill/dev-architect/refresh/REFRESH.md +31 -0
  20. package/skill/dev-architect/refresh/sources.json +239 -0
  21. package/skill/dev-chronicle/SKILL.md +52 -0
  22. package/skill/dev-chronicle/agents/openai.yaml +4 -0
  23. package/skill/dev-chronicle/references/conventions.md +113 -0
  24. package/skill/dev-chronicle/references/styles.md +75 -0
  25. package/skill/dev-chronicle/refresh/REFRESH.md +3 -0
  26. package/skill/dev-chronicle/refresh/sources.json +6 -0
  27. package/skill/dev-debug/SKILL.md +43 -0
  28. package/skill/dev-debug/agents/openai.yaml +4 -0
  29. package/skill/dev-debug/references/conventions.md +113 -0
  30. package/skill/dev-debug/references/loop-ladder.md +20 -0
  31. package/skill/dev-debug/refresh/REFRESH.md +3 -0
  32. package/skill/dev-debug/refresh/sources.json +6 -0
  33. package/skill/dev-implement/SKILL.md +81 -0
  34. package/skill/dev-implement/agents/openai.yaml +4 -0
  35. package/skill/dev-implement/assets/workflows/implement-children.js +12 -0
  36. package/skill/dev-implement/references/ask-route.md +80 -0
  37. package/skill/dev-implement/references/changelog-and-chronicle.md +21 -0
  38. package/skill/dev-implement/references/conventions.md +113 -0
  39. package/skill/dev-implement/references/ledger-and-resume.md +52 -0
  40. package/skill/dev-implement/references/parallel-children.md +35 -0
  41. package/skill/dev-implement/references/worktrees.md +63 -0
  42. package/skill/dev-implement/refresh/REFRESH.md +3 -0
  43. package/skill/dev-implement/refresh/sources.json +6 -0
  44. package/skill/dev-implement/scripts/children.mjs +479 -0
  45. package/skill/dev-implement/scripts/effective-policy.mjs +694 -0
  46. package/skill/dev-implement/scripts/evidence-check.mjs +107 -0
  47. package/skill/dev-implement/scripts/evidence-upload.mjs +181 -0
  48. package/skill/dev-implement/scripts/learning.mjs +41 -0
  49. package/skill/dev-implement/scripts/lib/approval.mjs +1182 -0
  50. package/skill/dev-implement/scripts/lib/gh.mjs +101 -0
  51. package/skill/dev-implement/scripts/preflight.mjs +153 -0
  52. package/skill/dev-implement/scripts/questions.mjs +381 -0
  53. package/skill/dev-implement/scripts/reclaim.mjs +116 -0
  54. package/skill/dev-implement/scripts/recovery.mjs +213 -0
  55. package/skill/dev-implement/scripts/worktree.mjs +991 -0
  56. package/skill/dev-intake/SKILL.md +80 -0
  57. package/skill/dev-intake/agents/openai.yaml +4 -0
  58. package/skill/dev-intake/references/ask-route.md +80 -0
  59. package/skill/dev-intake/references/brief-template.md +89 -0
  60. package/skill/dev-intake/references/conventions.md +113 -0
  61. package/skill/dev-intake/refresh/REFRESH.md +3 -0
  62. package/skill/dev-intake/refresh/sources.json +6 -0
  63. package/skill/dev-intake/scripts/brief-lint.mjs +87 -0
  64. package/skill/dev-intake/scripts/lib/approval.mjs +1182 -0
  65. package/skill/dev-intake/scripts/questions.mjs +381 -0
  66. package/skill/dev-plan/SKILL.md +54 -0
  67. package/skill/dev-plan/agents/openai.yaml +4 -0
  68. package/skill/dev-plan/references/ask-route.md +80 -0
  69. package/skill/dev-plan/references/conventions.md +113 -0
  70. package/skill/dev-plan/references/plan-format.md +82 -0
  71. package/skill/dev-plan/refresh/REFRESH.md +3 -0
  72. package/skill/dev-plan/refresh/sources.json +6 -0
  73. package/skill/dev-plan/scripts/lib/approval.mjs +1182 -0
  74. package/skill/dev-plan/scripts/plan-lint.mjs +319 -0
  75. package/skill/dev-plan/scripts/questions.mjs +381 -0
  76. package/skill/dev-review/SKILL.md +76 -0
  77. package/skill/dev-review/agents/openai.yaml +4 -0
  78. package/skill/dev-review/assets/review-known-patterns.md.template +32 -0
  79. package/skill/dev-review/references/conventions.md +113 -0
  80. package/skill/dev-review/references/cross-agent.md +39 -0
  81. package/skill/dev-review/references/dispatch-prompts.md +121 -0
  82. package/skill/dev-review/references/security-axis.md +45 -0
  83. package/skill/dev-review/refresh/REFRESH.md +5 -0
  84. package/skill/dev-review/refresh/sources.json +6 -0
  85. package/skill/dev-setup/SKILL.md +113 -0
  86. package/skill/dev-setup/agents/openai.yaml +4 -0
  87. package/skill/dev-setup/assets/agents-section.md.template +24 -0
  88. package/skill/dev-setup/assets/dev-profile.md.template +102 -0
  89. package/skill/dev-setup/assets/factory-board.yml.template +162 -0
  90. package/skill/dev-setup/assets/hooks/decision-nudge.mjs +30 -0
  91. package/skill/dev-setup/assets/hooks/prompt-skill-mention.mjs +29 -0
  92. package/skill/dev-setup/assets/hooks/session-end.mjs +14 -0
  93. package/skill/dev-setup/assets/hooks/session-start.mjs +178 -0
  94. package/skill/dev-setup/assets/hooks/ship-guard.mjs +738 -0
  95. package/skill/dev-setup/assets/hooks/skill-activated.mjs +47 -0
  96. package/skill/dev-setup/assets/hooks/stop-heartbeat.mjs +14 -0
  97. package/skill/dev-setup/assets/workflows/implement-children.js +12 -0
  98. package/skill/dev-setup/references/ask-route.md +80 -0
  99. package/skill/dev-setup/references/conventions.md +113 -0
  100. package/skill/dev-setup/references/github-app.md +286 -0
  101. package/skill/dev-setup/references/harness-facts.md +169 -0
  102. package/skill/dev-setup/references/stack-playbooks.md +124 -0
  103. package/skill/dev-setup/refresh/REFRESH.md +13 -0
  104. package/skill/dev-setup/refresh/sources.json +602 -0
  105. package/skill/dev-setup/scripts/effective-policy.mjs +694 -0
  106. package/skill/dev-setup/scripts/questions.mjs +381 -0
  107. package/skill/dev-setup/scripts/ship-policy.mjs +195 -0
  108. package/skill/dev-ship/SKILL.md +56 -0
  109. package/skill/dev-ship/agents/openai.yaml +4 -0
  110. package/skill/dev-ship/references/conventions.md +113 -0
  111. package/skill/dev-ship/references/runbook.md +72 -0
  112. package/skill/dev-ship/refresh/REFRESH.md +3 -0
  113. package/skill/dev-ship/refresh/sources.json +6 -0
  114. package/skill/dev-ship/scripts/lib/approval.mjs +1182 -0
  115. package/skill/dev-ship/scripts/ship-gate.mjs +485 -0
  116. package/skill/dev-status/SKILL.md +56 -0
  117. package/skill/dev-status/agents/openai.yaml +4 -0
  118. package/skill/dev-status/references/conventions.md +113 -0
  119. package/skill/dev-status/refresh/REFRESH.md +3 -0
  120. package/skill/dev-status/refresh/sources.json +6 -0
  121. package/skill/dev-status/scripts/effective-policy.mjs +694 -0
  122. package/skill/dev-status/scripts/status.mjs +311 -0
  123. package/skill/skill-maintainer/SKILL.md +82 -0
  124. package/skill/skill-maintainer/agents/openai.yaml +4 -0
  125. package/skill/skill-maintainer/references/release-ops.md +61 -0
  126. package/skill/skill-maintainer/references/standards.md +137 -0
  127. package/skill/skill-maintainer/refresh/REFRESH.md +40 -0
  128. package/skill/skill-maintainer/refresh/sources.json +103 -0
  129. package/skill/skill-scan/SKILL.md +59 -0
  130. package/skill/skill-scan/agents/openai.yaml +4 -0
  131. package/skill/skill-scan/refresh/REFRESH.md +18 -0
  132. package/skill/skill-scan/refresh/sources.json +133 -0
  133. package/skill/skill-scan/scripts/lib/skillspector.mjs +226 -0
  134. package/skill/skill-scan/scripts/skill-scan.mjs +1002 -0
  135. package/skill/skillify/SKILL.md +108 -0
  136. package/skill/skillify/agents/openai.yaml +4 -0
  137. package/skill/skillify/assets/templates/README.md.template +26 -0
  138. package/skill/skillify/assets/templates/REFRESH.md.template +16 -0
  139. package/skill/skillify/assets/templates/SKILL.md.template +27 -0
  140. package/skill/skillify/assets/templates/evals.json.template +12 -0
  141. package/skill/skillify/assets/templates/openai.yaml.template +4 -0
  142. package/skill/skillify/assets/templates/skill.test.ts.template +27 -0
  143. package/skill/skillify/assets/templates/sources.json.template +6 -0
  144. package/skill/skillify/assets/templates/trigger-queries.json.template +1 -0
  145. package/skill/skillify/references/authoring.md +103 -0
  146. package/skill/skillify/references/eval-playbook.md +112 -0
  147. package/skill/skillify/refresh/REFRESH.md +23 -0
  148. package/skill/skillify/refresh/sources.json +6 -0
  149. package/skill/skillify/scripts/scaffold-skill.mjs +334 -0
  150. package/skill/skillify/scripts/trigger-check.mjs +209 -0
  151. package/skill/vegafactory-setup/SKILL.md +77 -0
  152. package/skill/vegafactory-setup/agents/openai.yaml +4 -0
  153. package/skill/vegafactory-setup/assets/control-room/boards.md.template +17 -0
  154. package/skill/vegafactory-setup/assets/control-room/decisions.md.template +5 -0
  155. package/skill/vegafactory-setup/assets/control-room/group.md.template +44 -0
  156. package/skill/vegafactory-setup/assets/control-room/onboarding/dispatcher-box.md.template +86 -0
  157. package/skill/vegafactory-setup/assets/control-room/onboarding/new-repo.md.template +13 -0
  158. package/skill/vegafactory-setup/assets/control-room/onboarding/new-teammate.md.template +12 -0
  159. package/skill/vegafactory-setup/assets/control-room/org.md.template +60 -0
  160. package/skill/vegafactory-setup/assets/control-room/people.csv.template +2 -0
  161. package/skill/vegafactory-setup/assets/control-room/repos.md.template +7 -0
  162. package/skill/vegafactory-setup/assets/control-room/rules/CODEOWNERS.template +6 -0
  163. package/skill/vegafactory-setup/assets/control-room/rules/README.md.template +9 -0
  164. package/skill/vegafactory-setup/assets/control-room/rules/stats-privacy.md.template +44 -0
  165. package/skill/vegafactory-setup/assets/control-room/templates/README.md.template +9 -0
  166. package/skill/vegafactory-setup/references/control-room.md +149 -0
  167. package/skill/vegafactory-setup/references/conventions.md +113 -0
  168. package/skill/vegafactory-setup/refresh/REFRESH.md +5 -0
  169. package/skill/vegafactory-setup/refresh/sources.json +6 -0
  170. package/skill-integrity.json +252 -0
@@ -0,0 +1,40 @@
1
+ # Refresh contract — skill-maintainer
2
+
3
+ Instructions for the scheduled refresh agent (and any human running a manual refresh). This file, together with `sources.json`, is the complete freshness contract for this skill.
4
+
5
+ ## What this skill claims
6
+
7
+ - **Durable content** (`SKILL.md` workflows and operating rules, `references/release-ops.md`): repo process, elaborating `.vegastack/dev.md` — the refresh agent NEVER edits these. If the process changes, that is a normal human PR, not a refresh.
8
+ - **Volatile standards claims**: sentences marked `<!-- source: SOURCE-ID -->` inside `references/standards.md`. These carry the tri-harness standards — discovery paths, frontmatter rules, numeric context budgets, install surfaces. They are the compliance basis of every skill in this repo, so **all standards changes are semantic drift requiring a human-reviewed PR**: the agent may propose edits to marked sentences only, in the same PR as the registry update that evidences the change, and a maintainer must review before merge. The hard-limits table in `SKILL.md` mirrors several marked sentences; update it in the same PR (this is the one sanctioned SKILL.md touch, and only when a mirrored number changed).
9
+ - **Volatile layer** (the only file the agent edits freely): `refresh/sources.json` — the source registry and staleness snapshot (checksums, retrieval times).
10
+
11
+ ## How to refresh
12
+
13
+ The deterministic runner is repo-shared, hosted at `tooling/refresh/`. Run from the repo root:
14
+
15
+ 1. **Deterministic pass first** (no LLM judgment):
16
+ `node tooling/refresh/refresh-evidence.mjs --registry skills/repo-tooling/skill-maintainer/refresh/sources.json`
17
+ drift/stale/unavailable results are the work-list. Exit 1 with a critical entry means fail-closed: the run must not be silently skipped. All four sources here are critical.
18
+ 2. **Accept verified changes** in the same code path:
19
+ `node tooling/refresh/refresh-evidence.mjs --registry skills/repo-tooling/skill-maintainer/refresh/sources.json --accept-baselines`
20
+ This writes registry, cache, and drift report together — never hand-edit checksums, versions, or timestamps; they must always come from a run. Baselines are runner-seeded; when a new source is added or a verified change is accepted, this accept-baselines invocation is the only sanctioned way to update them.
21
+ 3. **Semantic verification** for every source the deterministic pass flagged: read the changed page (fetch the registry URL), decide whether any `<!-- source: X -->` marked sentence in `references/standards.md` (or the mirrored SKILL.md hard-limits row) is now wrong, and propose the minimal edit. Unlike version-pin registries, checksum drift here is presumed meaningful until a human reads the diff — these pages define the standards themselves. Editorial churn may be accepted silently only after that read.
22
+ 4. **One standing refresh PR**, branch `refresh/weekly`, force-updated on every run (never stacked duplicates; the weekly workflow .github/workflows/refresh.yml maintains it). PR body lists: each changed source, old→new checksum, links to the evidence, and which marked sentences changed and why. A maintainer review is mandatory before merge.
23
+ 5. Changes to durable content, workflows, tests, or anything outside `refresh/` and marked sentences are out of scope for a refresh PR — CI enforces this (refresh-guard workflow).
24
+
25
+ ## Cadence and thresholds
26
+
27
+ Weekly scheduled run. Every `thresholdDays` in `sources.json` is ≥ 14 (2× cadence) so one missed run never breaches a threshold.
28
+
29
+ ## What counts as drift worth a PR
30
+
31
+ - Any text-scope checksum change on any of the four sources → read the page; if a discovery path, frontmatter rule, numeric budget, or install surface changed, propose the marked-sentence edit and flag it prominently — downstream, every skill in `skills/` may need re-checking against the new standard (that re-check is a separate human task, never part of the refresh PR).
32
+ - A source page moves to an UNVERIFIED area (e.g. a documented path disappears) → move the claim into the UNVERIFIED register in `references/standards.md` rather than asserting the old fact.
33
+ - A critical source is unavailable or a redirect leaves the approved host set → investigate immediately; host changes require a deliberate `approvedHosts` update in the shared runner (out of refresh-PR scope — open an issue).
34
+
35
+ ## Never
36
+
37
+ - Never edit unmarked sentences, workflows, or `references/release-ops.md`.
38
+ - Never hand-write checksum/version/timestamp values.
39
+ - Never merge a standards change without human review — this registry has no auto-acceptable semantic drift.
40
+ - Never archive third-party documentation bodies in this repo (claim metadata, URLs, hashes, and concise excerpts only).
@@ -0,0 +1,103 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "policy": {
4
+ "criticalTopics": [
5
+ "skills-standard"
6
+ ],
7
+ "defaultChecksumScope": "html-text-v1",
8
+ "offline": "Use cached metadata only; fail closed when a critical entry is missing or older than thresholdDays.",
9
+ "drift": "All four sources are the compliance basis for this skill: any drift requires reading the changed page and a human-reviewed PR updating the marked sentences in references/standards.md (and the mirrored hard-limits table in SKILL.md); never auto-apply.",
10
+ "copyright": "Store claim metadata, URLs, hashes, headers, and concise excerpts only; never archive whole third-party documentation corpora.",
11
+ "cadence": "weekly automated refresh; thresholdDays must be >= 14 (2x cadence) so a single missed run never breaches a threshold"
12
+ },
13
+ "sources": [
14
+ {
15
+ "id": "AGENTSKILLS-SPEC",
16
+ "service": "agentskills.io specification",
17
+ "kind": "official-spec",
18
+ "stability": "open-standard",
19
+ "thresholdDays": 14,
20
+ "critical": true,
21
+ "urls": {
22
+ "primary": "https://agentskills.io/specification"
23
+ },
24
+ "versionDetection": {
25
+ "type": "manual-review"
26
+ },
27
+ "topics": [
28
+ "skills-standard"
29
+ ],
30
+ "affected": [
31
+ "ref:standards"
32
+ ],
33
+ "checksum": "f2c589e5fd5e4b4684fdf6567262c24dea6a3b14eca795ab1dca3c4b7a3e1eea",
34
+ "retrievedAt": "2026-08-29T05:57:04.887Z"
35
+ },
36
+ {
37
+ "id": "CLAUDE-CODE-SKILLS",
38
+ "service": "Claude Code skills documentation",
39
+ "kind": "official-docs",
40
+ "stability": "vendor-harness",
41
+ "thresholdDays": 14,
42
+ "critical": true,
43
+ "urls": {
44
+ "primary": "https://code.claude.com/docs/en/skills"
45
+ },
46
+ "versionDetection": {
47
+ "type": "manual-review"
48
+ },
49
+ "topics": [
50
+ "skills-standard"
51
+ ],
52
+ "affected": [
53
+ "ref:standards"
54
+ ],
55
+ "checksum": "92f9993b6c2cb79be4ecf3d2785c310978d4daf308df6a0d4d076845028b170b",
56
+ "retrievedAt": "2026-08-29T05:57:04.887Z"
57
+ },
58
+ {
59
+ "id": "CODEX-SKILLS",
60
+ "service": "Codex skills documentation",
61
+ "kind": "official-docs",
62
+ "stability": "vendor-harness",
63
+ "thresholdDays": 14,
64
+ "critical": true,
65
+ "urls": {
66
+ "primary": "https://developers.openai.com/codex/skills"
67
+ },
68
+ "versionDetection": {
69
+ "type": "manual-review"
70
+ },
71
+ "topics": [
72
+ "skills-standard"
73
+ ],
74
+ "affected": [
75
+ "ref:standards"
76
+ ],
77
+ "checksum": "e6ede44b27e6195c1920bdfb2f3f15e548f3ba34feeffad3a0cb23c54a0f63b9",
78
+ "retrievedAt": "2026-08-29T05:57:04.887Z"
79
+ },
80
+ {
81
+ "id": "HERMES-SKILLS",
82
+ "service": "Hermes Agent skills documentation",
83
+ "kind": "official-docs",
84
+ "stability": "vendor-harness",
85
+ "thresholdDays": 14,
86
+ "critical": true,
87
+ "urls": {
88
+ "primary": "https://hermes-agent.nousresearch.com/docs/user-guide/features/skills"
89
+ },
90
+ "versionDetection": {
91
+ "type": "manual-review"
92
+ },
93
+ "topics": [
94
+ "skills-standard"
95
+ ],
96
+ "affected": [
97
+ "ref:standards"
98
+ ],
99
+ "checksum": "d3cbea81c2c03c6ab480848c79d75d4914109898509344b774428579cc642223",
100
+ "retrievedAt": "2026-08-29T05:57:04.887Z"
101
+ }
102
+ ]
103
+ }
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: skill-scan
3
+ description: Scan agent skills for prompt-injection, rogue-agent, and supply-chain findings with NVIDIA SkillSpector, and hold the suppression baseline that records which findings are accepted and why. Use when asked to scan or vet skills, to judge whether a third-party or downloaded skill is safe to install, when a scan blocks and its findings need triage, when a file edit expires a coverage acceptance and it needs re-adjudicating, or when dev-implement's Verify gate or a project's Ship guard runs the scan. Not for reviewing a code diff against its brief and plan (dev-review, whose security axis consumes this scan's report), authoring or auditing a skill (skillify), or repo release standards (skill-maintainer).
4
+ ---
5
+
6
+ # skill-scan
7
+
8
+ An installed skill runs with your agent's authority, so a skill you did not read is a program you did not read. This guard scans skills — yours and strangers' — with NVIDIA SkillSpector, and holds the record of every finding anyone decided to accept.
9
+
10
+ Nearest neighbors: `dev-implement` runs this at its Verify gate and a project's `## Ship` runbook runs it before publishing; `dev-review`'s Security axis consumes the report this produces and triages what sits below the blocking bar; `skillify` audits a skill's completeness, which is a different question entirely.
11
+
12
+ ## Running it
13
+
14
+ Run from the project root, with `<path-to-skill-scan>` standing for wherever this skill is installed:
15
+
16
+ ```sh
17
+ node <path-to-skill-scan>/scripts/skill-scan.mjs --json # the gate: reads the knobs and the project baseline
18
+ node <path-to-skill-scan>/scripts/skill-scan.mjs --llm # adds the semantic pass — advisory, never a gate
19
+ node <path-to-skill-scan>/scripts/skill-scan.mjs --root ~/Downloads/some-skill # vet a skill you did not write
20
+ node <path-to-skill-scan>/scripts/skill-scan.mjs --no-provision # this run installs and upgrades nothing
21
+ ```
22
+
23
+ **Exit codes are the contract: 0 pass (or skipped) · 1 pass-with-warnings · 2 blocked.** Exit 2 stops the hand-back — fix the finding, or take it to the operator for a justified baseline entry. Widening a rule to reach green is the one move this guard exists to prevent.
24
+
25
+ ## The two knobs, both in `.vegastack/dev.md`
26
+
27
+ - `skill-scan:` names the root to scan — in a repo that builds a bundle, the **built** one, because that is what the world installs. `none` (or no line) turns the scan off and the guard says plainly that it skipped. A profile it cannot *read* is a different answer and blocks, so the gate can never disable itself by being run from the wrong directory.
28
+ - `skillspector-update:` takes `off | notify | auto`, and an absent line reads as `auto`: `auto` installs SkillSpector when absent and upgrades it before every scan (any failure falls back to the installed copy and the scan continues), `notify` reports the newest release and changes nothing, `off` makes no network call. The mode is read from the profile on **every** run, `--root` included — `--root` chooses what to scan, never whether this machine may be written to. `--no-provision` opts a single run out.
29
+
30
+ The guard finds the CLI through whatever channel installed it — uv, Homebrew, or pipx — and runs it by absolute path, so it works when the agent's shell `PATH` differs from the operator's. Only a scanner no channel reports *and* that is not on `PATH` refuses, and that message names every remedy.
31
+
32
+ Discovery reads exactly two levels: a skill sitting directly under the scan root, and a skill one group deep. Anything else holding a skill manifest blocks rather than being skipped — buried deeper, dot-prefixed, or behind a symlink, which is never followed. An unscanned skill nobody mentions looks exactly like a clean one.
33
+
34
+ ## The baseline — three sections, and the discipline behind them
35
+
36
+ `.vegastack/skillspector-baseline.json` is applied by convention when no `--root` is given. An explicit `--root` never inherits it: a rule written for your own content should not silence a finding in someone else's skill.
37
+
38
+ | Section | Accepts | Keyed by |
39
+ |---|---|---|
40
+ | `rules` | a finding class this project has judged and accepted | literal `id` and `path` |
41
+ | `fingerprints` | one specific occurrence | content hash |
42
+ | `coverage` | a file the scanner could not finish reading | `skill` + `file` + `sha256` |
43
+
44
+ `coverage` is ours rather than SkillSpector's, whose baseline suppresses findings only and cannot express "I could not finish reading this" — which any skill shipping ordinary JavaScript will hit. An acceptance covers exactly the file it names and exactly the bytes it names: **editing the file expires its acceptance**, and the fix is to re-adjudicate — confirm the reason code and degraded-analyzer count still hold, record the fresh digest, and append one sentence saying what changed — never to widen the entry.
45
+
46
+ Two rules the guard enforces rather than trusts:
47
+
48
+ - **Matchers are literal.** `*`, `?`, `[` and `]` are rejected. A single `{"id": "*"}` once silenced every finding while the run reported success, and a fix that rejected `*` was bypassed by `?*` immediately. Naming the file is the only "as narrow as its cause" a guard can check.
49
+ - **Every entry carries a `reason` with a "Still flag if:" clause** — the same discipline as `dev-review`'s known-patterns file. A suppression with no exception clause is a blind spot, and a rule scoped `id:` with no `path:` is a repo-wide one.
50
+
51
+ It blocks on any unsuppressed HIGH or CRITICAL finding and never on the aggregate risk score, which is inflated by documentation of the very mechanics being scanned and deflated by unrelated suppressions. A degraded scan blocks too: a run whose analyzer failed reports a *higher* score with fewer filtered findings, so its silence proves nothing. An upgrade that changed anything is reported before the findings, because a new finding after an upgrade is the tool having learned something.
52
+
53
+ ## Vetting a skill you did not write
54
+
55
+ Point `--root` at the directory before it reaches your agent. The report carries each finding's rule, severity, and `file:line`, plus every entry the baseline suppressed, so the judgement is traceable rather than a score taken on trust. Treat a hit as a candidate finding, not a verdict — and never downgrade an unexplained HIGH or CRITICAL on the strength of who published it.
56
+
57
+ ## A scan with no issue attached
58
+
59
+ A standalone scan, or the pre-publish guard in a project's `## Ship` runbook, has no review comment to land in, so its findings go to `dev-intake` as a `risky` issue — a finding posted somewhere convenient is a finding nobody owns. Offer the operator one `risky` issue whose brief carries the findings, their locations, and what is known about each cause; intake's questions, scope call, and approval follow.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "skill-scan"
3
+ short_description: "Scan and vet agent skills for vulnerabilities"
4
+ default_prompt: "Use $skill-scan to scan this project's skills and triage the findings."
@@ -0,0 +1,18 @@
1
+ # Refresh contract — skill-scan
2
+
3
+ The scan discipline itself (exit codes, the baseline's three sections, the literal-matcher rule, the "Still flag if:" clause) asserts nothing that can go stale. What is volatile is `scripts/lib/skillspector.mjs`, which parses the output of third-party commands and calls a third-party API — the six sources below.
4
+
5
+ What the registry tracks, and why each one is load-bearing rather than decorative: every parser below fails **silently** if its upstream surface changes — returning "not installed", which the guard reports as a missing scanner instead of a parsing bug. That silence is the whole reason these are registered.
6
+
7
+ | Source | The claim it holds up | Breaks how |
8
+ |---|---|---|
9
+ | `SS-INSTALL` | `uv tool install git+https://github.com/NVIDIA/skillspector.git` is upstream's documented install, and the package is not on PyPI | the install command installs nothing, or the wrong thing |
10
+ | `SS-VERSION-OUTPUT` | `skillspector --version` prints `SkillSpector v<x.y.z>` on **stdout**, warnings on stderr | version reporting goes null; the baseline-pin warning stops firing |
11
+ | `SS-RELEASES-API` | the GitHub releases endpoint shape, and its 60-request/hour unauthenticated limit | `notify` silently reports nothing |
12
+ | `UV-TOOL-LIST` | `uv tool list --show-paths` prints `- skillspector (/abs/path)` | uv-installed scanners read as absent |
13
+ | `BREW-PREFIX` | `brew list --versions` exits non-zero when absent, while `brew --prefix` exits **0 with a path for any known formula** whether or not it is installed | brew detection returns a path that does not exist |
14
+ | `PIPX-LIST` | `pipx list --short` exits 0 even with nothing installed, so its output must be parsed | pipx-installed scanners read as absent, or absence reads as present |
15
+
16
+ These six moved here from `dev-review` on 03-09-2026 with issue #113, when the guard became its own skill; the registry entries are unchanged, and their `affected` paths were already skill-relative.
17
+
18
+ Drift in any of them means reading the changed surface and updating both the parser and its dated comment in the same reviewed PR — never auto-applying. The parsers each have a unit test pinning the exact output shape, so a corrected parser has a failing test to satisfy.
@@ -0,0 +1,133 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "policy": {
4
+ "defaultChecksumScope": "html-text-v1",
5
+ "offline": "Use cached metadata only; fail closed when an entry is missing or older than thresholdDays.",
6
+ "drift": "Read the changed surface, then update the parser AND its dated comment in one reviewed PR; never auto-apply. Each parser has a unit test pinning the output shape.",
7
+ "copyright": "Store claim metadata, URLs, hashes and concise excerpts only; never archive third-party documentation.",
8
+ "cadence": "weekly scheduled refresh; thresholdDays must be >= 14 (2x cadence) so one missed run never breaches a threshold"
9
+ },
10
+ "sources": [
11
+ {
12
+ "id": "SS-INSTALL",
13
+ "service": "SkillSpector install instructions (uv; not on PyPI)",
14
+ "kind": "official-docs",
15
+ "stability": "vendor-docs",
16
+ "thresholdDays": 14,
17
+ "critical": true,
18
+ "urls": {
19
+ "primary": "https://github.com/NVIDIA/SkillSpector"
20
+ },
21
+ "versionDetection": {
22
+ "type": "manual-review"
23
+ },
24
+ "topics": [
25
+ "skillspector-install"
26
+ ],
27
+ "affected": [
28
+ "scripts/lib/skillspector.mjs",
29
+ "README.md"
30
+ ]
31
+ },
32
+ {
33
+ "id": "SS-VERSION-OUTPUT",
34
+ "service": "skillspector --version output shape (stdout vs stderr)",
35
+ "kind": "official-docs",
36
+ "stability": "vendor-docs",
37
+ "thresholdDays": 14,
38
+ "critical": true,
39
+ "urls": {
40
+ "primary": "https://github.com/NVIDIA/SkillSpector"
41
+ },
42
+ "versionDetection": {
43
+ "type": "manual-review"
44
+ },
45
+ "topics": [
46
+ "skillspector-version"
47
+ ],
48
+ "affected": [
49
+ "scripts/lib/skillspector.mjs"
50
+ ]
51
+ },
52
+ {
53
+ "id": "SS-RELEASES-API",
54
+ "service": "GitHub releases endpoint shape and unauthenticated rate limit",
55
+ "kind": "official-docs",
56
+ "stability": "vendor-docs",
57
+ "thresholdDays": 14,
58
+ "critical": false,
59
+ "urls": {
60
+ "primary": "https://docs.github.com/en/rest/releases/releases"
61
+ },
62
+ "versionDetection": {
63
+ "type": "manual-review"
64
+ },
65
+ "topics": [
66
+ "skillspector-releases"
67
+ ],
68
+ "affected": [
69
+ "scripts/lib/skillspector.mjs"
70
+ ]
71
+ },
72
+ {
73
+ "id": "UV-TOOL-LIST",
74
+ "service": "uv tool list --show-paths output shape",
75
+ "kind": "official-docs",
76
+ "stability": "vendor-docs",
77
+ "thresholdDays": 14,
78
+ "critical": true,
79
+ "urls": {
80
+ "primary": "https://docs.astral.sh/uv/concepts/tools/"
81
+ },
82
+ "versionDetection": {
83
+ "type": "manual-review"
84
+ },
85
+ "topics": [
86
+ "channel-uv"
87
+ ],
88
+ "affected": [
89
+ "scripts/lib/skillspector.mjs"
90
+ ]
91
+ },
92
+ {
93
+ "id": "BREW-PREFIX",
94
+ "service": "brew list --versions vs brew --prefix exit semantics",
95
+ "kind": "official-docs",
96
+ "stability": "vendor-docs",
97
+ "thresholdDays": 14,
98
+ "critical": false,
99
+ "urls": {
100
+ "primary": "https://docs.brew.sh/Manpage"
101
+ },
102
+ "versionDetection": {
103
+ "type": "manual-review"
104
+ },
105
+ "topics": [
106
+ "channel-brew"
107
+ ],
108
+ "affected": [
109
+ "scripts/lib/skillspector.mjs"
110
+ ]
111
+ },
112
+ {
113
+ "id": "PIPX-LIST",
114
+ "service": "pipx list --short exit code and output",
115
+ "kind": "official-docs",
116
+ "stability": "vendor-docs",
117
+ "thresholdDays": 14,
118
+ "critical": false,
119
+ "urls": {
120
+ "primary": "https://pipx.pypa.io/stable/docs/"
121
+ },
122
+ "versionDetection": {
123
+ "type": "manual-review"
124
+ },
125
+ "topics": [
126
+ "channel-pipx"
127
+ ],
128
+ "affected": [
129
+ "scripts/lib/skillspector.mjs"
130
+ ]
131
+ }
132
+ ]
133
+ }
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+ // Everything about the SkillSpector CLI itself — where it is, installing it,
3
+ // upgrading it, reading its version. `skill-scan.mjs` keeps owning the scan and
4
+ // the verdict and calls in here once, before scanning.
5
+ //
6
+ // Named for the tool, not for its role: this repo's own machinery is "skill
7
+ // scan", and the third-party binary is always "skillspector" by its exact name,
8
+ // so the generic word stays free (operator's rule, 01-09-2026).
9
+ //
10
+ // Every command runs through an injected `run`, and every path check through an
11
+ // injected `exists`, so unit tests never install software or touch the disk.
12
+ // Self-contained (ships with skill-scan; no cross-skill imports, no dependencies).
13
+ import { execFileSync } from 'node:child_process';
14
+ import { existsSync } from 'node:fs';
15
+ import { join } from 'node:path';
16
+
17
+ // Upstream's own documented quick install, verified 01-09-2026 against the
18
+ // SkillSpector README, which documents uv and no other package manager. The git
19
+ // URL is not optional: the package is NOT published to PyPI (HTTP 404), so a
20
+ // bare `pip install skillspector` can never work.
21
+ export const INSTALL_COMMAND = ['uv', ['tool', 'install', 'git+https://github.com/NVIDIA/skillspector.git']];
22
+
23
+ // Per channel, in probe order. `detect` proves the tool is actually installed;
24
+ // `locate` turns that into an absolute executable path.
25
+ const UPGRADE = {
26
+ uv: ['uv', ['tool', 'upgrade', 'skillspector']],
27
+ brew: ['brew', ['upgrade', 'skillspector']],
28
+ pipx: ['pipx', ['upgrade', 'skillspector']],
29
+ };
30
+
31
+ // A command runner that never throws: a non-zero exit is data, not an
32
+ // exception, because "brew is not installed here" is an ordinary answer to
33
+ // "where is skillspector". stderr is folded into stdout so a failure message
34
+ // survives for the report.
35
+ // stdio mode for a discarded fd, hoisted out of quote-adjacency: SkillSpector reads the
36
+ // bare word beside its own closing quote as a removal cue and fails closed on the whole
37
+ // file (skill-maintainer's standards.md, known behaviours). Same value, same behaviour.
38
+ const DISCARD = 'ignore';
39
+
40
+ export function defaultRun(cmd, args, { timeoutMs = 300_000 } = {}) {
41
+ try {
42
+ const stdout = execFileSync(cmd, args, {
43
+ encoding: 'utf8',
44
+ stdio: [DISCARD, 'pipe', 'pipe'],
45
+ // `env` is passed explicitly, as skill-scan.mjs does: under Bun a mutated
46
+ // process.env is NOT inherited by execFileSync children.
47
+ env: { ...process.env },
48
+ timeout: timeoutMs,
49
+ maxBuffer: 16 * 1024 * 1024,
50
+ });
51
+ return { ok: true, stdout };
52
+ } catch (error) {
53
+ const out = ((error.stdout?.toString() ?? '') + (error.stderr?.toString() ?? '')).trim();
54
+ return { ok: false, stdout: out || error.message || '' };
55
+ }
56
+ }
57
+
58
+ // `uv tool list --show-paths` prints one line per tool and one indented line per
59
+ // executable it installed:
60
+ // skillspector v2.11.0 (/home/x/.local/share/uv/tools/skillspector)
61
+ // - skillspector (/home/x/.local/bin/skillspector)
62
+ // The executable line is the one that matters — the first is the venv, which is
63
+ // not runnable. Anchored on both sides so `skillspector-extra` cannot match.
64
+ export function parseUvToolList(text) {
65
+ for (const line of String(text ?? '').split('\n')) {
66
+ const match = /^-\s+skillspector\s+\((.+)\)\s*$/.exec(line.trim());
67
+ if (match) return match[1];
68
+ }
69
+ return null;
70
+ }
71
+
72
+ // `pipx list --short` exits 0 whether or not anything is installed (verified
73
+ // 01-09-2026 — it prints "nothing has been installed with pipx" and succeeds),
74
+ // so the exit code proves nothing and the listing has to be read.
75
+ export function parsePipxList(text) {
76
+ return String(text ?? '')
77
+ .split('\n')
78
+ .some((line) => /^skillspector(\s|$)/.test(line.trim()));
79
+ }
80
+
81
+ // Ask each channel where it put the executable, in order, and believe only a
82
+ // path that is actually on disk.
83
+ //
84
+ // The existence check is not defensive padding: `brew --prefix <formula>` exits
85
+ // 0 and prints a path for any formula it KNOWS, installed or not — verified
86
+ // 01-09-2026, where it named /opt/homebrew/opt/skillspector while nothing was
87
+ // installed there. Detection therefore runs `brew list --versions` first, and
88
+ // the check below is the backstop for every channel.
89
+ export function locateSkillspector({ run = defaultRun, exists = existsSync } = {}) {
90
+ const believe = (channel, path) => (path && exists(path) ? { channel, path } : null);
91
+
92
+ const uv = run('uv', ['tool', 'list', '--show-paths']);
93
+ if (uv.ok) {
94
+ const found = believe('uv', parseUvToolList(uv.stdout));
95
+ if (found) return found;
96
+ }
97
+
98
+ if (run('brew', ['list', '--versions', 'skillspector']).ok) {
99
+ const prefix = run('brew', ['--prefix', 'skillspector']);
100
+ if (prefix.ok && prefix.stdout.trim()) {
101
+ const found = believe('brew', join(prefix.stdout.trim(), 'bin', 'skillspector'));
102
+ if (found) return found;
103
+ }
104
+ }
105
+
106
+ const pipx = run('pipx', ['list', '--short']);
107
+ if (pipx.ok && parsePipxList(pipx.stdout)) {
108
+ const dir = run('pipx', ['environment', '--value', 'PIPX_BIN_DIR']);
109
+ if (dir.ok && dir.stdout.trim()) {
110
+ const found = believe('pipx', join(dir.stdout.trim(), 'skillspector'));
111
+ if (found) return found;
112
+ }
113
+ }
114
+
115
+ return null;
116
+ }
117
+
118
+
119
+ // Strip C0/C1 controls (ANSI escapes included) from anything a command printed:
120
+ // this text reaches a terminal report, and package-manager output carries names
121
+ // from outside the repo. Mirrors skill-scan.mjs's own `safe()`.
122
+ function safe(text) {
123
+ // eslint-disable-next-line no-control-regex
124
+ return String(text).replace(/[\u0000-\u001f\u007f-\u009f]/g, '?');
125
+ }
126
+
127
+ // `skillspector --version` prints "SkillSpector v2.11.0" on STDOUT while its
128
+ // missing-API-key warnings go to stderr (verified 01-09-2026). `run` hands back
129
+ // stdout alone on success, so the warnings can never contaminate the match.
130
+ export function readVersion({ path, run = defaultRun }) {
131
+ const result = run(path, ['--version']);
132
+ if (!result.ok) return null;
133
+ const match = /SkillSpector\s+v?(\d+\.\d+\.\d+\S*)/i.exec(result.stdout);
134
+ return match ? match[1] : null;
135
+ }
136
+
137
+ // The lines a package manager reported moving. uv prints one `+ pkg==x` /
138
+ // `- pkg==y` line per dependency it changed; anything else simply yields none.
139
+ function changedLines(stdout) {
140
+ return String(stdout ?? '')
141
+ .split('\n')
142
+ .map((line) => safe(line.trim()))
143
+ .filter((line) => /^[+-]\s*\S/.test(line));
144
+ }
145
+
146
+ // Install when absent, upgrade when present — and never throw: a machine
147
+ // without a network, without uv, or with a locked package manager must fall
148
+ // back to whatever is already installed and let the scan proceed. Only a
149
+ // SkillSpector that cannot be found at all blocks, and that is skill-scan's
150
+ // call, not this function's.
151
+ //
152
+ // No version check runs first, deliberately. `uv tool upgrade` moves the whole
153
+ // dependency tree while the version string can hold steady (verified
154
+ // 01-09-2026: langsmith 0.11.2 -> 0.12.0 under an unchanged v2.11.0), so
155
+ // comparing versions would report "current" about a tool that just changed.
156
+ export function provisionSkillspector({ mode, located, run = defaultRun }) {
157
+ const idle = { action: 'none', before: null, after: null, changed: [], message: '' };
158
+
159
+ // `notify` reports; it never touches the machine. The release lookup that
160
+ // makes it useful belongs to the caller, which owns the network policy.
161
+ if (mode !== 'auto') return located ? { ...idle, before: readVersion({ path: located.path, run }) } : idle;
162
+
163
+ if (!located) {
164
+ const [cmd, args] = INSTALL_COMMAND;
165
+ const result = run(cmd, args);
166
+ if (!result.ok) {
167
+ return { ...idle, action: 'failed', message: safe(result.stdout), changed: changedLines(result.stdout) };
168
+ }
169
+ // `after` stays null: reading it needs the path, and only a fresh locate
170
+ // knows where the install landed. The caller re-locates and fills it in.
171
+ return { action: 'installed', before: null, after: null, changed: changedLines(result.stdout), message: '' };
172
+ }
173
+
174
+ const upgrade = UPGRADE[located.channel];
175
+ if (!upgrade) {
176
+ return { ...idle, message: `no upgrade command is known for the ${safe(String(located.channel))} channel` };
177
+ }
178
+
179
+ const before = readVersion({ path: located.path, run });
180
+ const result = run(upgrade[0], upgrade[1]);
181
+ if (!result.ok) {
182
+ return { action: 'failed', before, after: before, changed: [], message: safe(result.stdout) };
183
+ }
184
+ return {
185
+ action: 'upgraded',
186
+ before,
187
+ after: readVersion({ path: located.path, run }),
188
+ changed: changedLines(result.stdout),
189
+ message: '',
190
+ };
191
+ }
192
+
193
+
194
+ // Upstream's releases feed. Unauthenticated and rate-limited to 60 requests an
195
+ // hour per IP (verified 01-09-2026), which `notify` stays far inside because it
196
+ // asks once per guard run and only in that mode.
197
+ export const RELEASES_URL = 'https://api.github.com/repos/NVIDIA/SkillSpector/releases/latest';
198
+
199
+ async function fetchReleaseJson(url) {
200
+ // GitHub rejects requests without a User-Agent.
201
+ const response = await fetch(url, {
202
+ headers: { accept: 'application/vnd.github+json', 'user-agent': 'vegastack-skill-scan' },
203
+ signal: AbortSignal.timeout(10_000),
204
+ });
205
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
206
+ return response.json();
207
+ }
208
+
209
+ // The newest published release, or null. EVERY failure path is null and never a
210
+ // throw: `notify` is a courtesy line in a report, and a guard must not turn a
211
+ // flaky network into a verdict. A tag that is not a plain version (a nightly, a
212
+ // moved pointer) is rejected rather than reported as a version.
213
+ export async function latestRelease({ fetchJson = fetchReleaseJson, url = RELEASES_URL } = {}) {
214
+ let body;
215
+ try {
216
+ body = await fetchJson(url);
217
+ } catch {
218
+ return null;
219
+ }
220
+ if (!body || typeof body !== 'object') return null;
221
+ const tag = typeof body.tag_name === 'string' ? body.tag_name.trim() : '';
222
+ const match = /^v?(\d+\.\d+\.\d+\S*)$/.exec(tag);
223
+ return match ? match[1] : null;
224
+ }
225
+
226
+ export const UPGRADE_COMMANDS = UPGRADE;