@lmzhen/dsh-skill-usage 0.1.0-rc.49 → 0.1.0-rc.50

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/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Service } from "@deepseek-ai/cordis";
2
2
  import z from "@deepseek-ai/schemastery";
3
- import { bumpPatch, bumpUse, bumpView, evolutionIoAdapter, getRecord, loadUsage, markAgentCreated, saveUsage, skillsRoot } from "@lmzhen/dsh-evolution-core";
3
+ import { bumpPatch, bumpUse, bumpView, evolutionIoAdapter, getRecord, loadUsage, markAgentCreated, mutateUsage, skillsRoot } from "@lmzhen/dsh-evolution-core";
4
4
  //#region lib/types/index.js
5
5
  /**
6
6
  * Skill usage telemetry service for the evolution family.
@@ -11,41 +11,45 @@ var SkillUsageRegistry = class extends Service {
11
11
  static Config = z.object({ root: z.string().default("") });
12
12
  root;
13
13
  io;
14
- usage = null;
15
14
  chain = Promise.resolve();
16
15
  constructor(ctx, config = {}) {
17
16
  super(ctx, "skillUsage");
18
17
  this.root = config.root || skillsRoot();
19
18
  this.io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
20
19
  }
21
- async map() {
22
- if (!this.usage) this.usage = await loadUsage(this.root, this.io);
23
- return this.usage;
24
- }
25
- async flush() {
26
- if (this.usage) await saveUsage(this.root, this.usage, this.io);
27
- }
28
- /** Serialize read-modify-write cycles so concurrent record calls never lose updates. */
20
+ /**
21
+ * Serialize read-modify-write cycles so concurrent record calls never lose
22
+ * updates, then run each cycle as ONE atomic transact (rc.50 P2-2): the map
23
+ * is read from disk inside the lock, so a second process that shares
24
+ * DSH_HOME cannot interleave its own RMW between our read and write.
25
+ */
29
26
  mutate(task) {
30
- const run = this.chain.then(async () => task(await this.map()));
27
+ const run = this.chain.then(async () => {
28
+ let outcome = void 0;
29
+ await mutateUsage(this.root, this.io, async (map) => {
30
+ outcome = await task(map);
31
+ });
32
+ return outcome;
33
+ });
31
34
  this.chain = run.then(() => void 0, () => void 0);
32
35
  return run;
33
36
  }
34
37
  async record(name, kind, at = /* @__PURE__ */ new Date()) {
35
- await this.mutate(async (map) => {
38
+ await this.mutate((map) => {
36
39
  if (kind === "use") bumpUse(map, name, at);
37
40
  else if (kind === "view") bumpView(map, name, at);
38
41
  else bumpPatch(map, name, at);
39
- await this.flush();
40
42
  });
41
43
  }
42
- report() {
43
- return this.mutate((map) => Promise.resolve(new Map(map)));
44
+ /** Read-only snapshot of the sidecar (no disk write — reading never mutates). */
45
+ async report() {
46
+ const run = this.chain.then(async () => new Map(await loadUsage(this.root, this.io)));
47
+ this.chain = run.then(() => void 0, () => void 0);
48
+ return run;
44
49
  }
45
50
  async markAgentCreated(name) {
46
- await this.mutate(async (map) => {
51
+ await this.mutate((map) => {
47
52
  markAgentCreated(map, name);
48
- await this.flush();
49
53
  });
50
54
  }
51
55
  /**
@@ -55,9 +59,8 @@ var SkillUsageRegistry = class extends Service {
55
59
  * mutation maturity is not inflated by mere creation.
56
60
  */
57
61
  async ensureRecord(name) {
58
- await this.mutate(async (map) => {
62
+ await this.mutate((map) => {
59
63
  getRecord(map, name);
60
- await this.flush();
61
64
  });
62
65
  }
63
66
  /**
@@ -66,32 +69,29 @@ var SkillUsageRegistry = class extends Service {
66
69
  * a state transition, not a content mutation.
67
70
  */
68
71
  async markArchived(name, at = /* @__PURE__ */ new Date()) {
69
- await this.mutate(async (map) => {
72
+ await this.mutate((map) => {
70
73
  const record = map.get(name);
71
74
  if (record) {
72
75
  record.state = "archived";
73
76
  record.archived_at = at.toISOString();
74
77
  }
75
- await this.flush();
76
78
  });
77
79
  }
78
80
  /**
79
- * Drop the in-memory cache once queued work drains, so the next `map()` reads
80
- * the file again. The curator writes the sidecar directly; without this, the
81
- * next tool telemetry flush would re-cover its quality/state/pinned writes.
81
+ * Barrier for external writers: every mutate reads the sidecar fresh from
82
+ * disk (rc.50 P2-2 transact), so a curator direct-write is visible on the
83
+ * next call without a cache flush; this waits for queued work to drain.
82
84
  */
83
85
  async invalidate() {
84
86
  await this.chain;
85
- this.usage = null;
86
87
  }
87
88
  /** Write feedback-derived quality onto the usage sidecar; curator reads it. */
88
89
  async setQuality(name, score, warn) {
89
- await this.mutate(async (map) => {
90
+ await this.mutate((map) => {
90
91
  const record = map.get(name);
91
92
  if (!record) return;
92
93
  record.quality_score = score;
93
94
  record.quality_warn = warn;
94
- await this.flush();
95
95
  });
96
96
  }
97
97
  };
@@ -18,14 +18,17 @@ export declare class SkillUsageRegistry extends Service {
18
18
  static Config: Schema<Config>;
19
19
  readonly root: string;
20
20
  private readonly io;
21
- private usage;
22
21
  private chain;
23
22
  constructor(ctx: Context, config?: Config);
24
- private map;
25
- flush(): Promise<void>;
26
- /** Serialize read-modify-write cycles so concurrent record calls never lose updates. */
23
+ /**
24
+ * Serialize read-modify-write cycles so concurrent record calls never lose
25
+ * updates, then run each cycle as ONE atomic transact (rc.50 P2-2): the map
26
+ * is read from disk inside the lock, so a second process that shares
27
+ * DSH_HOME cannot interleave its own RMW between our read and write.
28
+ */
27
29
  private mutate;
28
30
  record(name: string, kind: 'use' | 'view' | 'patch', at?: Date): Promise<void>;
31
+ /** Read-only snapshot of the sidecar (no disk write — reading never mutates). */
29
32
  report(): Promise<UsageMap>;
30
33
  markAgentCreated(name: string): Promise<void>;
31
34
  /**
@@ -42,9 +45,9 @@ export declare class SkillUsageRegistry extends Service {
42
45
  */
43
46
  markArchived(name: string, at?: Date): Promise<void>;
44
47
  /**
45
- * Drop the in-memory cache once queued work drains, so the next `map()` reads
46
- * the file again. The curator writes the sidecar directly; without this, the
47
- * next tool telemetry flush would re-cover its quality/state/pinned writes.
48
+ * Barrier for external writers: every mutate reads the sidecar fresh from
49
+ * disk (rc.50 P2-2 transact), so a curator direct-write is visible on the
50
+ * next call without a cache flush; this waits for queued work to drain.
48
51
  */
49
52
  invalidate(): Promise<void>;
50
53
  /** Write feedback-derived quality onto the usage sidecar; curator reads it. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-skill-usage",
3
3
  "description": "Skill usage telemetry service (community build)",
4
- "version": "0.1.0-rc.49",
4
+ "version": "0.1.0-rc.50",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -33,16 +33,16 @@
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
35
  "@deepseek-ai/schemastery": "^3.18.1",
36
- "@lmzhen/dsh-evolution-core": "^0.1.0-rc.49"
36
+ "@lmzhen/dsh-evolution-core": "^0.1.0-rc.50"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@deepseek-ai/cordis": "^4.0.1",
40
40
  "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
41
- "@lmzhen/dsh-evolution-io": "^0.1.0-rc.49"
41
+ "@lmzhen/dsh-evolution-io": "^0.1.0-rc.50"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
45
- "@lmzhen/dsh-evolution-core": "^0.1.0-rc.49",
46
- "@lmzhen/dsh-evolution-io": "^0.1.0-rc.49"
45
+ "@lmzhen/dsh-evolution-core": "^0.1.0-rc.50",
46
+ "@lmzhen/dsh-evolution-io": "^0.1.0-rc.50"
47
47
  }
48
48
  }