@ccoalm/ccl-skills 0.1.1 → 0.3.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 (30) hide show
  1. package/README.md +12 -0
  2. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_init_policy_matrix.sh +93 -16
  3. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_probe_result.sh +10 -0
  4. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +249 -5
  5. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate_abort_leak.sh +394 -0
  6. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/model-prompt-evaluation.md +7 -0
  7. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/external-ui-ux-quality-benchmarks.md +50 -1
  8. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-audit.md +1 -0
  9. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +1 -1
  10. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +59 -1
  11. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/rule-consolidation.md +3 -1
  12. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +34 -0
  13. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +16 -4
  14. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +391 -14
  15. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/register-firing-path-resolution.rb +74 -0
  16. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +74 -33
  17. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_skill_catalog.sh +11 -4
  18. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_verdict_differential.sh +421 -0
  19. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_round_attribution.sh +576 -0
  20. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_source_refuted.sh +176 -0
  21. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_regression_runner_lanes.sh +101 -0
  22. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_root_depth.sh +6 -2
  23. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/ci-fixtures-and-flake-control.md +1 -1
  24. package/dist/assets/release.json +66 -21
  25. package/dist/cli.js +23 -2
  26. package/dist/update-notice.d.ts +71 -0
  27. package/dist/update-notice.js +173 -0
  28. package/dist/version-check.d.ts +2 -0
  29. package/dist/version-check.js +7 -0
  30. package/package.json +1 -1
@@ -0,0 +1,71 @@
1
+ import { mkdirSync, renameSync, unlinkSync, writeFileSync, readFileSync } from "node:fs";
2
+ export type Cache = {
3
+ latest?: string;
4
+ lastCheckedAt?: string;
5
+ notifiedVersion?: string;
6
+ notifiedAt?: string;
7
+ };
8
+ export type NoticeInput = {
9
+ current: string;
10
+ cache?: Cache;
11
+ now: number;
12
+ env: Record<string, string | undefined>;
13
+ stdoutTTY: boolean;
14
+ stderrTTY: boolean;
15
+ columns?: number;
16
+ json: boolean;
17
+ };
18
+ export type Verdict = {
19
+ show: false;
20
+ reason: string;
21
+ } | {
22
+ show: true;
23
+ reason: "update-available";
24
+ latest: string;
25
+ };
26
+ export declare const TTL_MS: number;
27
+ export declare const CACHE_FILE = "version-check.json";
28
+ /** Numeric precedence. Returns 0 when either side is not a stable version, so
29
+ * an unparseable value can never be reported as newer. */
30
+ export declare function compareVersions(a: string, b: string): number;
31
+ export declare function pickLatest(packument: unknown): string | null;
32
+ /** Fails open toward refreshing: an absent, malformed, or future timestamp all
33
+ * mean "check again", never "assume fresh". */
34
+ export declare function needsRefresh(cache: Cache | undefined, now: number, ttl?: number): boolean;
35
+ export declare function refreshAllowed(input: NoticeInput): boolean;
36
+ export declare function shouldNotify(input: NoticeInput): Verdict;
37
+ /** Plain text only: colour is an enhancement, never the state carrier, and
38
+ * every line fits the narrow-terminal floor this notice is gated on. */
39
+ export declare function formatNotice(current: string, latest: string): string;
40
+ export declare function cachePath(root: string): string;
41
+ export type EmitDeps = {
42
+ stderr: (text: string) => void;
43
+ spawnCheck: () => void;
44
+ };
45
+ /** The only entry the CLI calls. It is handed a stderr writer and nothing that
46
+ * can reach stdout, so the notice structurally cannot pollute primary output.
47
+ * Returns the sanitized reason code; it never throws and never changes exit. */
48
+ export declare function emitNotice(input: NoticeInput, file: string, deps: EmitDeps): string;
49
+ export type RefreshOptions = {
50
+ file: string;
51
+ now?: number;
52
+ fetchImpl?: (url: string, init: unknown) => Promise<{
53
+ ok: boolean;
54
+ json: () => Promise<unknown>;
55
+ }>;
56
+ timeoutMs?: number;
57
+ };
58
+ /** Runs in a detached child, never on the caller's path. The check time is
59
+ * stamped even on failure so an offline machine retries once per TTL, not once
60
+ * per command; a failed check never erases a previously known version. */
61
+ export declare function refresh(options: RefreshOptions): Promise<void>;
62
+ export declare function readCache(path: string, read?: typeof readFileSync): Cache | undefined;
63
+ export type WriteDeps = {
64
+ writeFileSync?: typeof writeFileSync;
65
+ renameSync?: typeof renameSync;
66
+ unlinkSync?: typeof unlinkSync;
67
+ mkdirSync?: typeof mkdirSync;
68
+ };
69
+ /** Temp file in the target directory, then rename: a reader sees the old cache
70
+ * or the whole new one, never a truncated one, and never an EXDEV failure. */
71
+ export declare function writeCacheAtomic(path: string, cache: Cache, deps?: WriteDeps): void;
@@ -0,0 +1,173 @@
1
+ // Update notice: a terminal side channel, never primary output.
2
+ // Display is bound to eligibility, cache freshness, and cooldown; the network
3
+ // check runs detached so no invocation pays for it. Every failure path is
4
+ // swallowed — a version check must never change what the command itself does.
5
+ import { dirname, join } from "node:path";
6
+ import { mkdirSync, renameSync, unlinkSync, writeFileSync, readFileSync } from "node:fs";
7
+ export const TTL_MS = 24 * 60 * 60 * 1000;
8
+ export const CACHE_FILE = "version-check.json";
9
+ const MIN_COLUMNS = 60;
10
+ const OPT_OUT = ["CCL_SKILLS_NO_UPDATE_NOTIFIER", "NO_UPDATE_NOTIFIER"];
11
+ const CI_VARS = [
12
+ "CI",
13
+ "CONTINUOUS_INTEGRATION",
14
+ "BUILD_NUMBER",
15
+ "GITHUB_ACTIONS",
16
+ "GITLAB_CI",
17
+ "BUILDKITE",
18
+ "CIRCLECI",
19
+ "TRAVIS",
20
+ "TEAMCITY_VERSION",
21
+ "JENKINS_URL",
22
+ ];
23
+ // Releases accept only stable MAJOR.MINOR.PATCH, so anything else — prerelease,
24
+ // build metadata, or a hostile string — is not a version we will ever suggest.
25
+ const STABLE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
26
+ const set = (env, names) => names.some((name) => (env[name] ?? "") !== "");
27
+ /** Numeric precedence. Returns 0 when either side is not a stable version, so
28
+ * an unparseable value can never be reported as newer. */
29
+ export function compareVersions(a, b) {
30
+ if (!STABLE.test(a) || !STABLE.test(b))
31
+ return 0;
32
+ const left = a.split(".").map(Number), right = b.split(".").map(Number);
33
+ for (let i = 0; i < 3; i++)
34
+ if (left[i] !== right[i])
35
+ return left[i] - right[i];
36
+ return 0;
37
+ }
38
+ export function pickLatest(packument) {
39
+ const tags = packument?.["dist-tags"];
40
+ const latest = tags?.latest;
41
+ return typeof latest === "string" && STABLE.test(latest) ? latest : null;
42
+ }
43
+ /** Fails open toward refreshing: an absent, malformed, or future timestamp all
44
+ * mean "check again", never "assume fresh". */
45
+ export function needsRefresh(cache, now, ttl = TTL_MS) {
46
+ const at = Date.parse(cache?.lastCheckedAt ?? "");
47
+ return Number.isNaN(at) || at > now || now - at >= ttl;
48
+ }
49
+ /** The display gates that do not depend on cache contents. Shared by the notice
50
+ * and the background check so `--json`, CI, and opt-out make no network call. */
51
+ function displayAllowed(input) {
52
+ if (set(input.env, OPT_OUT))
53
+ return { ok: false, reason: "opt-out" };
54
+ if (set(input.env, CI_VARS) || !input.stdoutTTY || !input.stderrTTY)
55
+ return { ok: false, reason: "non-interactive" };
56
+ if (input.json)
57
+ return { ok: false, reason: "structured-output" };
58
+ // A pty with no window size reports 0: that is "width unknown", not "zero
59
+ // cells wide", so it must not be treated as a narrow terminal.
60
+ if (input.columns !== undefined && input.columns > 0 && input.columns < MIN_COLUMNS)
61
+ return { ok: false, reason: "narrow-terminal" };
62
+ return { ok: true, reason: "eligible" };
63
+ }
64
+ export function refreshAllowed(input) {
65
+ return displayAllowed(input).ok;
66
+ }
67
+ export function shouldNotify(input) {
68
+ const gate = displayAllowed(input);
69
+ if (!gate.ok)
70
+ return { show: false, reason: gate.reason };
71
+ const latest = input.cache?.latest;
72
+ if (!latest)
73
+ return { show: false, reason: "no-cache" };
74
+ if (compareVersions(latest, input.current) <= 0)
75
+ return { show: false, reason: "up-to-date" };
76
+ const notifiedAt = Date.parse(input.cache?.notifiedAt ?? "");
77
+ if (input.cache?.notifiedVersion === latest &&
78
+ !Number.isNaN(notifiedAt) &&
79
+ input.now - notifiedAt < TTL_MS)
80
+ return { show: false, reason: "cooldown" };
81
+ return { show: true, reason: "update-available", latest };
82
+ }
83
+ /** Plain text only: colour is an enhancement, never the state carrier, and
84
+ * every line fits the narrow-terminal floor this notice is gated on. */
85
+ export function formatNotice(current, latest) {
86
+ return [
87
+ `Update available: ${current} -> ${latest}`,
88
+ "Run: ccl-skills update --yes",
89
+ "Silence: CCL_SKILLS_NO_UPDATE_NOTIFIER=1",
90
+ ].join("\n");
91
+ }
92
+ export function cachePath(root) {
93
+ return join(root, CACHE_FILE);
94
+ }
95
+ /** The only entry the CLI calls. It is handed a stderr writer and nothing that
96
+ * can reach stdout, so the notice structurally cannot pollute primary output.
97
+ * Returns the sanitized reason code; it never throws and never changes exit. */
98
+ export function emitNotice(input, file, deps) {
99
+ try {
100
+ const cache = input.cache ?? readCache(file);
101
+ const resolved = { ...input, cache };
102
+ const verdict = shouldNotify(resolved);
103
+ if (verdict.show) {
104
+ deps.stderr(formatNotice(input.current, verdict.latest));
105
+ writeCacheAtomic(file, {
106
+ ...cache,
107
+ notifiedVersion: verdict.latest,
108
+ notifiedAt: new Date(input.now).toISOString(),
109
+ });
110
+ }
111
+ if (refreshAllowed(resolved) && needsRefresh(cache, input.now))
112
+ deps.spawnCheck();
113
+ return verdict.reason;
114
+ }
115
+ catch {
116
+ return "unavailable";
117
+ }
118
+ }
119
+ const REGISTRY = "https://registry.npmjs.org/@ccoalm%2fccl-skills";
120
+ /** Runs in a detached child, never on the caller's path. The check time is
121
+ * stamped even on failure so an offline machine retries once per TTL, not once
122
+ * per command; a failed check never erases a previously known version. */
123
+ export async function refresh(options) {
124
+ const now = options.now ?? Date.now(), previous = readCache(options.file) ?? {};
125
+ let latest = null;
126
+ try {
127
+ const response = await (options.fetchImpl ?? fetch)(REGISTRY, {
128
+ headers: { Accept: "application/vnd.npm.install-v1+json" },
129
+ signal: AbortSignal.timeout(options.timeoutMs ?? 5000),
130
+ });
131
+ if (response.ok)
132
+ latest = pickLatest(await response.json());
133
+ }
134
+ catch {
135
+ /* offline, throttled, or malformed: keep the previous value */
136
+ }
137
+ writeCacheAtomic(options.file, {
138
+ ...previous,
139
+ ...(latest ? { latest } : {}),
140
+ lastCheckedAt: new Date(now).toISOString(),
141
+ });
142
+ }
143
+ export function readCache(path, read = readFileSync) {
144
+ try {
145
+ const parsed = JSON.parse(read(path, "utf8"));
146
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
147
+ ? parsed
148
+ : undefined;
149
+ }
150
+ catch {
151
+ return undefined;
152
+ }
153
+ }
154
+ /** Temp file in the target directory, then rename: a reader sees the old cache
155
+ * or the whole new one, never a truncated one, and never an EXDEV failure. */
156
+ export function writeCacheAtomic(path, cache, deps = {}) {
157
+ const write = deps.writeFileSync ?? writeFileSync, rename = deps.renameSync ?? renameSync, unlink = deps.unlinkSync ?? unlinkSync, temp = join(dirname(path), `.${CACHE_FILE}.tmp-${process.pid}`);
158
+ try {
159
+ // Without this the state root is absent until a host install runs, the
160
+ // write fails every time, and the caller reschedules the check forever.
161
+ (deps.mkdirSync ?? mkdirSync)(dirname(path), { recursive: true });
162
+ write(temp, `${JSON.stringify(cache)}\n`, { encoding: "utf8", mode: 0o600 });
163
+ rename(temp, path);
164
+ }
165
+ catch {
166
+ try {
167
+ unlink(temp);
168
+ }
169
+ catch {
170
+ /* nothing to clean up */
171
+ }
172
+ }
173
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ // Detached entrypoint for the background version check. It is spawned with
3
+ // stdio ignored and unref'd, so it cannot slow, block, or outlive-block the
4
+ // command that scheduled it, and it exits 0 whatever the registry does.
5
+ import { paths } from "./paths.js";
6
+ import { cachePath, refresh } from "./update-notice.js";
7
+ refresh({ file: cachePath(paths().root) }).then(() => process.exit(0), () => process.exit(0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ccoalm/ccl-skills",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Self-contained CCL Skills installer for Claude Code, Codex, and OpenCode",
5
5
  "keywords": ["agent-skills", "claude-code", "codex", "opencode", "developer-tools"],
6
6
  "type": "module",