@hasna/skills 0.8.11 → 0.9.1

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.
@@ -1,5 +1,6 @@
1
1
  import type { EcsDispatcherConfig } from "../sdk/execution/dispatchers/ecs.js";
2
2
  import type { FrozenAdmission, AttemptRecord } from "../sdk/execution/types.js";
3
+ import { type PureReviewedBundle } from "./runtime-pure-contract.js";
3
4
  export declare const RUNTIME_MAX_INPUT_BYTES = 100000;
4
5
  export declare const RUNTIME_MAX_BUNDLE_BYTES = 1000000;
5
6
  export declare const RUNTIME_MAX_ARTIFACT_BYTES = 2000000;
@@ -9,11 +10,12 @@ export interface RuntimeConfig extends EcsDispatcherConfig {
9
10
  imageDigest: string;
10
11
  apiOrigin: string;
11
12
  /** Only independently reviewed exact bundles enter this first credential-free lane. */
12
- reviewedBundles: {
13
+ reviewedBundles: ({
13
14
  slug: "pdf-generate";
14
15
  version: string;
15
16
  sha256: string;
16
- }[];
17
+ executionContract?: never;
18
+ } | PureReviewedBundle)[];
17
19
  }
18
20
  export declare function readRuntimeConfig(env?: Record<string, string | undefined>): RuntimeConfig | null;
19
21
  export declare function runtimeInput(value: unknown): {
@@ -0,0 +1,51 @@
1
+ /** Trusted pure interface; publishing a skill never grants admission to it. */
2
+ import { type SkillBundleEntry } from "../lib/skill-bundle.js";
3
+ import type { FrozenAdmission, PureExecutionContract } from "../sdk/execution/types.js";
4
+ export declare const PURE_DESCRIPTOR: Readonly<{
5
+ id: "regex-test.v1";
6
+ runtime: "bun";
7
+ adapter: "skills-input-json.v1";
8
+ input: Readonly<{
9
+ patternBytes: 512;
10
+ textBytes: 4096;
11
+ totalBytes: 8192;
12
+ flags: "dgimsuvy";
13
+ }>;
14
+ output: "regex-matches.v1";
15
+ secrets: "none";
16
+ egress: "deny";
17
+ maxDurationMs: 5000;
18
+ maxMemoryMb: 512;
19
+ maxCpuUnits: 256;
20
+ stdoutBytes: 16384;
21
+ stderrBytes: 8192;
22
+ artifactsBytes: 0;
23
+ }>;
24
+ export declare const PURE_DESCRIPTOR_DIGEST: string;
25
+ export declare const PURE_LIMITS: Readonly<{
26
+ maxDurationMs: 5000;
27
+ maxMemoryMb: 512;
28
+ maxCpuUnits: 256;
29
+ maxArtifactsBytes: 0;
30
+ maxConcurrency: 1;
31
+ }>;
32
+ export interface PureInput {
33
+ pattern: string;
34
+ text: string;
35
+ flags: string;
36
+ }
37
+ export interface PureReviewedBundle {
38
+ slug: string;
39
+ version: string;
40
+ sha256: string;
41
+ tenantId: string;
42
+ imageDigest: string;
43
+ executionContract: PureExecutionContract;
44
+ }
45
+ /** JSON.parse alone silently discards duplicate keys, including escaped spellings. */
46
+ export declare function uniqueJson(text: string): unknown;
47
+ export declare function pureInput(value: unknown): PureInput;
48
+ export declare function assertPureContract(value: unknown): asserts value is PureExecutionContract;
49
+ export declare function assertPureAdmission(a: FrozenAdmission): PureExecutionContract;
50
+ export declare function inspectPureBundle(bytes: Uint8Array, contract: PureExecutionContract): Promise<SkillBundleEntry[]>;
51
+ export declare function validatePureOutput(stdout: string, input: PureInput): void;
@@ -22,6 +22,10 @@ export interface RuntimeJob {
22
22
  input?: {
23
23
  content: string;
24
24
  title?: string;
25
+ } | {
26
+ pattern: string;
27
+ text: string;
28
+ flags: string;
25
29
  };
26
30
  bundleBase64?: string;
27
31
  result?: RuntimeResult;
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import type { FrozenAdmission } from "../sdk/execution/types.js";
3
+ import { type PureInput } from "./runtime-pure-contract.js";
3
4
  import type { RuntimeResult } from "./runtime-store.js";
4
5
  export interface RuntimeWork {
5
6
  admission: FrozenAdmission;
@@ -7,7 +8,7 @@ export interface RuntimeWork {
7
8
  input: {
8
9
  content: string;
9
10
  title?: string;
10
- };
11
+ } | PureInput;
11
12
  }
12
13
  export interface SupervisorOptions {
13
14
  executable?: string;
@@ -2,6 +2,7 @@ import type { Database } from "bun:sqlite";
2
2
  import type { ApiPrincipal } from "./types.js";
3
3
  import type { SkillProfile, SkillSelection, StationSkillState, StationSkillStateInput } from "../types/skill-selection.js";
4
4
  export interface SkillSelectionStore {
5
+ profilesReferencingSkill(principal: ApiPrincipal, slug: string): Promise<string[]>;
5
6
  getProfile(principal: ApiPrincipal, id: string): Promise<SkillProfile | null>;
6
7
  /** null expected revision means create-only; null result means CAS conflict. */
7
8
  saveProfile(principal: ApiPrincipal, id: string, selections: SkillSelection[], expected: string | null): Promise<SkillProfile | null>;
@@ -15,6 +16,7 @@ export declare class MemorySkillSelectionStore implements SkillSelectionStore {
15
16
  private states;
16
17
  private key;
17
18
  getProfile(p: ApiPrincipal, id: string): Promise<SkillProfile | null>;
19
+ profilesReferencingSkill(p: ApiPrincipal, slug: string): Promise<string[]>;
18
20
  saveProfile(p: ApiPrincipal, id: string, selected: SkillSelection[], expected: string | null): Promise<SkillProfile | null>;
19
21
  getStationState(p: ApiPrincipal, id: string): Promise<StationSkillState | null>;
20
22
  saveStationState(p: ApiPrincipal, id: string, input: StationSkillStateInput): Promise<StationSkillState | null>;
@@ -22,15 +24,21 @@ export declare class MemorySkillSelectionStore implements SkillSelectionStore {
22
24
  export declare class SqliteSkillSelectionStore implements SkillSelectionStore {
23
25
  private db;
24
26
  constructor(db: Database);
27
+ profilesReferencingSkill(p: ApiPrincipal, slug: string): Promise<string[]>;
25
28
  getProfile(p: ApiPrincipal, id: string): Promise<SkillProfile | null>;
26
29
  saveProfile(p: ApiPrincipal, id: string, selected: SkillSelection[], expected: string | null): Promise<SkillProfile | null>;
27
30
  getStationState(p: ApiPrincipal, id: string): Promise<StationSkillState | null>;
28
31
  saveStationState(p: ApiPrincipal, id: string, input: StationSkillStateInput): Promise<StationSkillState | null>;
29
32
  }
30
33
  type Sql = (strings: TemplateStringsArray, ...values: unknown[]) => Promise<Row[]>;
34
+ type SqlTransaction = Sql;
35
+ interface SqlWithTransaction extends Sql {
36
+ begin<T>(fn: (tx: SqlTransaction) => Promise<T>): Promise<T>;
37
+ }
31
38
  export declare class PostgresSkillSelectionStore implements SkillSelectionStore {
32
39
  private sql;
33
- constructor(sql: Sql);
40
+ constructor(sql: SqlWithTransaction);
41
+ profilesReferencingSkill(p: ApiPrincipal, slug: string): Promise<string[]>;
34
42
  getProfile(p: ApiPrincipal, id: string): Promise<SkillProfile | null>;
35
43
  saveProfile(p: ApiPrincipal, id: string, selected: SkillSelection[], expected: string | null): Promise<SkillProfile | null>;
36
44
  getStationState(p: ApiPrincipal, id: string): Promise<StationSkillState | null>;
@@ -2,7 +2,7 @@ import { type OwnedBytes } from "../lib/skill-bundle.js";
2
2
  import type { SkillMeta } from "../lib/registry-types.js";
3
3
  import type { ArtifactStorage } from "./artifact-storage.js";
4
4
  import type { SkillsServerConfig } from "./config.js";
5
- import { type ApiPrincipal, type PublishSkillInput, type ServerPin, type ServerSkillRecord, type ServerSkillVersion, type SkillsProductStore } from "./types.js";
5
+ import { type ApiPrincipal, type PublishSkillInput, type ServerPin, type ServerSkillRecord, type ServerSkillVersion, type SkillLifecyclePatch, type SkillsProductStore } from "./types.js";
6
6
  /** Wire shape of a pin: the client-facing facts, without the storage columns. */
7
7
  export declare function pinPayload(pin: ServerPin): Record<string, unknown>;
8
8
  /**
@@ -20,6 +20,7 @@ export declare class SkillRequestError extends Error {
20
20
  readonly code: string;
21
21
  constructor(status: number, code: string, message: string);
22
22
  }
23
+ export declare function skillLifecyclePatch(body: Record<string, unknown>): SkillLifecyclePatch;
23
24
  /** SkillMeta shape for a published row, so a client can treat both kinds alike. */
24
25
  export declare function publishedSkillMeta(record: ServerSkillRecord): SkillMeta;
25
26
  /**
@@ -15,7 +15,7 @@ import { SqliteExecutionGrantStore } from "./execution-grant-store.js";
15
15
  */
16
16
  import { Database } from "bun:sqlite";
17
17
  import { SqliteSkillSelectionStore } from "./selection-store.js";
18
- import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillVersion, ServerSkillRecord, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
18
+ import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillVersion, ApiKeyScopeUpdateResult, ServerSkillRecord, SkillLifecyclePatch, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
19
19
  export interface SqliteStoreOptions {
20
20
  /** Apply pending migrations on open. Default true - it is what makes zero-config work. */
21
21
  migrate?: boolean;
@@ -52,6 +52,7 @@ export declare class SqliteSkillsStore implements SkillsProductStore {
52
52
  close(): Promise<void>;
53
53
  ensureBootstrapApiKey(token: string, principal?: Partial<ApiPrincipal>): Promise<void>;
54
54
  authenticateApiKeyHash(hash: string): Promise<ApiPrincipal | null>;
55
+ updateApiKeyScopes(actor: ApiPrincipal, keyId: string, expectedScopes: string[], addScopes: string[]): Promise<ApiKeyScopeUpdateResult>;
55
56
  createRun(input: CreateRunInput): Promise<ServerRunRecord>;
56
57
  listRuns(principal: ApiPrincipal, limit: number): Promise<ServerRunRecord[]>;
57
58
  getRun(principal: ApiPrincipal, id: string): Promise<ServerRunRecord | null>;
@@ -102,6 +103,8 @@ export declare class SqliteSkillsStore implements SkillsProductStore {
102
103
  publishSkill(input: PublishSkillInput): Promise<ServerSkillRecord>;
103
104
  listSkills(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
104
105
  getSkill(principal: ApiPrincipal, slug: string): Promise<ServerSkillRecord | null>;
106
+ setSkillLifecycle(principal: ApiPrincipal, slug: string, patch: SkillLifecyclePatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
107
+ private getSkillSync;
105
108
  updateSkill(principal: ApiPrincipal, slug: string, patch: UpdateSkillPatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
106
109
  deleteSkill(principal: ApiPrincipal, slug: string, tombstoneWindowMs: number): Promise<ServerSkillRecord | null>;
107
110
  purgeExpiredTombstones(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
@@ -1,6 +1,7 @@
1
1
  import { MemoryExecutionGrantStore, PostgresExecutionGrantStore } from "./execution-grant-store.js";
2
2
  import { MemorySkillSelectionStore, PostgresSkillSelectionStore } from "./selection-store.js";
3
- import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord, ServerSkillVersion, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
3
+ import type { ApiPrincipal, ClaimRunInput, CreateRunInput, PublishSkillInput, RunTransitionPatch, ServerArtifact, ServerPin, ServerRunLog, ServerRunRecord, ServerSkillBundle, ServerSkillRecord, ServerSkillVersion, ApiKeyScopeUpdateResult, SkillsProductStore, StoreBackendInfo, UpdateSkillPatch } from "./types.js";
4
+ import { type SkillLifecyclePatch } from "./types.js";
4
5
  import { type SqliteStoreOptions } from "./sqlite-store.js";
5
6
  export declare function createArtifactId(): string;
6
7
  export interface StoreOptions {
@@ -44,6 +45,7 @@ export declare class MemorySkillsStore implements SkillsProductStore {
44
45
  addApiKey(token: string, principal?: Partial<ApiPrincipal>): ApiPrincipal;
45
46
  ensureBootstrapApiKey(token: string, principal?: Partial<ApiPrincipal>): Promise<void>;
46
47
  authenticateApiKeyHash(hash: string): Promise<ApiPrincipal | null>;
48
+ updateApiKeyScopes(actor: ApiPrincipal, keyId: string, expectedScopes: string[], addScopes: string[]): Promise<ApiKeyScopeUpdateResult>;
47
49
  createRun(input: CreateRunInput): Promise<ServerRunRecord>;
48
50
  listRuns(principal: ApiPrincipal, limit: number): Promise<ServerRunRecord[]>;
49
51
  getRun(principal: ApiPrincipal, runId: string): Promise<ServerRunRecord | null>;
@@ -76,6 +78,7 @@ export declare class MemorySkillsStore implements SkillsProductStore {
76
78
  getSkillVersion(principal: ApiPrincipal, slug: string, version: string): Promise<ServerSkillVersion | null>;
77
79
  listSkills(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
78
80
  getSkill(principal: ApiPrincipal, slug: string): Promise<ServerSkillRecord | null>;
81
+ setSkillLifecycle(principal: ApiPrincipal, slug: string, patch: SkillLifecyclePatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
79
82
  updateSkill(principal: ApiPrincipal, slug: string, patch: UpdateSkillPatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
80
83
  deleteSkill(principal: ApiPrincipal, slug: string, tombstoneWindowMs: number): Promise<ServerSkillRecord | null>;
81
84
  purgeExpiredTombstones(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
@@ -118,6 +121,7 @@ export declare class PostgresSkillsStore implements SkillsProductStore {
118
121
  close(): Promise<void>;
119
122
  ensureBootstrapApiKey(token: string, principal?: Partial<ApiPrincipal>): Promise<void>;
120
123
  authenticateApiKeyHash(hash: string): Promise<ApiPrincipal | null>;
124
+ updateApiKeyScopes(actor: ApiPrincipal, keyId: string, expectedScopes: string[], addScopes: string[]): Promise<ApiKeyScopeUpdateResult>;
121
125
  /**
122
126
  * Run a callback under an RLS tenant or worker context, on one pooled
123
127
  * connection, for the duration of one transaction only.
@@ -163,6 +167,7 @@ export declare class PostgresSkillsStore implements SkillsProductStore {
163
167
  publishSkill(input: PublishSkillInput): Promise<ServerSkillRecord>;
164
168
  listSkills(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
165
169
  getSkill(principal: ApiPrincipal, slug: string): Promise<ServerSkillRecord | null>;
170
+ setSkillLifecycle(principal: ApiPrincipal, slug: string, patch: SkillLifecyclePatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
166
171
  updateSkill(principal: ApiPrincipal, slug: string, patch: UpdateSkillPatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
167
172
  deleteSkill(principal: ApiPrincipal, slug: string, tombstoneWindowMs: number): Promise<ServerSkillRecord | null>;
168
173
  purgeExpiredTombstones(principal: ApiPrincipal): Promise<ServerSkillRecord[]>;
@@ -45,6 +45,11 @@ export declare class SkillVersionExistsError extends Error {
45
45
  readonly attemptedSha256: string;
46
46
  constructor(slug: string, version: string, existingSha256: string, attemptedSha256: string);
47
47
  }
48
+ export declare class SkillLifecycleConflictError extends Error {
49
+ readonly slug: string;
50
+ readonly profiles: string[];
51
+ constructor(slug: string, profiles: string[]);
52
+ }
48
53
  export interface ApiPrincipal {
49
54
  apiKeyId: string;
50
55
  orgId: string;
@@ -55,6 +60,15 @@ export interface ApiPrincipal {
55
60
  role: string;
56
61
  scopes: string[];
57
62
  }
63
+ export type ApiKeyScopeUpdateResult = {
64
+ kind: "updated";
65
+ scopes: string[];
66
+ } | {
67
+ kind: "not_found";
68
+ } | {
69
+ kind: "stale";
70
+ scopes: string[];
71
+ };
58
72
  export interface ServerRunRecord {
59
73
  id: string;
60
74
  orgId: string;
@@ -162,6 +176,16 @@ export interface ServerSkillRecord {
162
176
  */
163
177
  tombstonedAt?: string;
164
178
  tombstonePurgeAfter?: string;
179
+ /** Catalog lifecycle state. Archived rows retain versions and bundles for explicit reads. */
180
+ lifecycle: "active" | "archived";
181
+ archivedAt?: string;
182
+ archiveReason?: string;
183
+ replacementSlug?: string;
184
+ }
185
+ export interface SkillLifecyclePatch {
186
+ lifecycle: "active" | "archived";
187
+ reason?: string;
188
+ replacementSlug?: string;
165
189
  }
166
190
  /**
167
191
  * Bundle bytes, addressed by their own digest.
@@ -313,6 +337,8 @@ export interface SkillsProductStore {
313
337
  */
314
338
  verifyConnectivity?(): Promise<void>;
315
339
  authenticateApiKeyHash(hash: string): Promise<ApiPrincipal | null>;
340
+ /** Guarded metadata-only scope admission for an existing key. */
341
+ updateApiKeyScopes?(actor: ApiPrincipal, keyId: string, expectedScopes: string[], addScopes: string[]): Promise<ApiKeyScopeUpdateResult>;
316
342
  ensureBootstrapApiKey?(token: string, principal?: Partial<ApiPrincipal>): Promise<void>;
317
343
  createRun(input: CreateRunInput): Promise<ServerRunRecord>;
318
344
  listRuns(principal: ApiPrincipal, limit: number): Promise<ServerRunRecord[]>;
@@ -350,6 +376,7 @@ export interface SkillsProductStore {
350
376
  * when the org has no skill by that slug, or when the row is tombstoned.
351
377
  */
352
378
  updateSkill(principal: ApiPrincipal, slug: string, patch: UpdateSkillPatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
379
+ setSkillLifecycle(principal: ApiPrincipal, slug: string, patch: SkillLifecyclePatch, expectedRevisionId?: string): Promise<ServerSkillRecord | null>;
353
380
  /**
354
381
  * Tombstone a skill instead of hard-deleting it (todos d061fcda): the row is stamped
355
382
  * tombstoned_at + tombstone_purge_after (now + tombstoneWindowMs) and kept, so reads
@@ -196,6 +196,73 @@ not proof of a timestamp-only change: review the complete current registration
196
196
  and its complementary sources before explicitly replacing an old witness.
197
197
  Capturing this witness does not write a policy or approve native registration.
198
198
 
199
+ ## Claude settings preferences
200
+
201
+ `captureClaudeSettings(canonicalSettingsPath)` returns a versioned
202
+ `claude-settings-v1` discovery source for the configured Claude user
203
+ `settings.json`. Use the canonical target when a reviewed home root alias is
204
+ present. The mode is accepted only in explicit reviewed Claude discovery; it
205
+ cannot stand in for a plugin manifest, executable, project settings review or
206
+ another agent's configuration.
207
+
208
+ The digest canonicalizes JSON object key order, preserves array order and
209
+ unknown numeric values without rounding, and excludes only these top-level
210
+ preferences and model selections with valid documented types:
211
+
212
+ - Booleans: `autoScrollEnabled`, `axScreenReader`, `emojiCompletionEnabled`,
213
+ `prefersReducedMotion`, `showTurnDuration`, `spinnerTipsEnabled`,
214
+ `syntaxHighlightingDisabled`, `terminalProgressBarEnabled`,
215
+ `terminalTitleFromRename`, `verbose`, `wheelScrollAccelerationEnabled`.
216
+ - Exact strings: `editorMode` (`normal`, `vim`), `tui` (`default`, `fullscreen`),
217
+ `viewMode` (`default`, `verbose`, `focus`).
218
+ - Built-in `model` values: `default`, `best`, `fable`, `fable[1m]`, `sonnet`,
219
+ `sonnet[1m]`, `opus`, `opus[1m]`, `haiku`, `opusplan`; exact model IDs
220
+ `claude-fable-5-1`, `claude-fable-5`, `claude-fable-5[1m]`, `claude-opus-5`,
221
+ `claude-sonnet-5`, `claude-haiku-4-5-20251001`, `claude-opus-4-6`,
222
+ `claude-sonnet-4-5`, `claude-sonnet-4-5-20250929`, `claude-opus-4-8`,
223
+ `claude-opus-4-8[1m]`, `claude-opus-4-7`, `claude-sonnet-4-6`,
224
+ `claude-opus-4-5-20251101`, `claude-opus-4-5`, `claude-haiku-4-5`,
225
+ `claude-fable-5-1[1m]`, `claude-opus-5[1m]`, `claude-opus-4-7[1m]`,
226
+ `claude-opus-4-6[1m]`, `claude-sonnet-4-6[1m]`.
227
+
228
+ The model exception follows the documented [model picker](https://code.claude.com/docs/en/model-config#setting-your-model)
229
+ and [model IDs](https://platform.claude.com/docs/en/models/overview), including
230
+ the documented [extended-context model-name suffix](https://code.claude.com/docs/en/model-config#extended-context)
231
+ only for the listed supported combinations. It permits
232
+ inference selection among this fixed list, including clearing the override. It
233
+ does not exempt provider or alias mappings, `modelOverrides`, `modelPicker`,
234
+ `modelSettings`, `availableModels`, environment or model-switch hooks. Arbitrary
235
+ custom, provider-specific, future or path-like model strings stay bound; there
236
+ is no prefix wildcard or dynamic model lookup. Non-string model values refuse.
237
+
238
+ Invalid values for the display preferences refuse capture and verification. Every
239
+ other field remains bound, including unknown future fields, hooks and their
240
+ exact commands, permissions, native skill suppression and synchronization,
241
+ plugin roots, marketplaces, environment and configuration precedence. Language,
242
+ output style, theme, and command-bearing status or file suggestion settings are
243
+ intentionally retained. These are narrow preference and inference-selection
244
+ exceptions, not general permission to change Claude configuration. The native
245
+ bridge guard continues to check its exact commands and native Skill policy.
246
+
247
+ Migration requires reviewing the current complete settings and complementary
248
+ plugin/loader sources, replacing the settings raw source with the captured
249
+ typed source in a reviewed discovery-input file, and using the normal hook
250
+ installation plan/apply operation. A stale prior raw hash is not evidence that
251
+ only preferences changed. Capture performs no write or automatic refresh; old
252
+ raw witnesses remain exact. Concurrent changes during an installation still
253
+ face full-byte preconditions so a preference edit cannot be overwritten.
254
+
255
+ The source must be a stable normalized regular file named `settings.json` with
256
+ strict UTF-8, no duplicate JSON keys and no trailing content. Limits are 1 MiB,
257
+ 1,024 top-level fields, depth 32, 65,536 nodes and 16,384 characters per string;
258
+ capture also consumes the shared discovery byte budget. Symlinks, special
259
+ nodes and changes during a read refuse. This witness covers the configured
260
+ user file; existing checks on project settings, root aliases and plugin sources
261
+ remain separate. It does not certify arbitrary command-line settings overrides
262
+ or guarantee when a running native client adopts a settings edit.
263
+ Those complementary checks retain their existing exact registration and hook
264
+ entry comparisons; this mode does not relax them for property-order changes.
265
+
199
266
  ## Verification and limits
200
267
 
201
268
  Unit tests cover provenance and mapping failures, offline/revoked authorities,
@@ -0,0 +1,5 @@
1
+ ALTER TABLE skills_registry ADD COLUMN lifecycle text NOT NULL DEFAULT 'active' CHECK (lifecycle IN ('active','archived'));
2
+ ALTER TABLE skills_registry ADD COLUMN archived_at timestamptz;
3
+ ALTER TABLE skills_registry ADD COLUMN archive_reason text;
4
+ ALTER TABLE skills_registry ADD COLUMN replacement_slug text;
5
+ CREATE INDEX IF NOT EXISTS skills_registry_org_lifecycle_idx ON skills_registry (org_id, lifecycle, slug);
@@ -0,0 +1,5 @@
1
+ ALTER TABLE skills_registry ADD COLUMN lifecycle text NOT NULL DEFAULT 'active' CHECK (lifecycle IN ('active','archived'));
2
+ ALTER TABLE skills_registry ADD COLUMN archived_at text;
3
+ ALTER TABLE skills_registry ADD COLUMN archive_reason text;
4
+ ALTER TABLE skills_registry ADD COLUMN replacement_slug text;
5
+ CREATE INDEX IF NOT EXISTS skills_registry_org_lifecycle_idx ON skills_registry (org_id, lifecycle, slug);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/skills",
3
- "version": "0.8.11",
3
+ "version": "0.9.1",
4
4
  "description": "Skills library for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {