sentinelayer-cli 0.29.1 → 0.30.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sentinelayer-cli",
3
- "version": "0.29.1",
3
+ "version": "0.30.1",
4
4
  "description": "Scaffold Sentinelayer spec/prompt/guide artifacts with secure browser auth and token bootstrap.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -10,6 +10,7 @@
10
10
  import fsp from "node:fs/promises";
11
11
  import path from "node:path";
12
12
  import crypto from "node:crypto";
13
+ import { spawnSync } from "node:child_process";
13
14
 
14
15
  import { recordProvisionedIdentity } from "../ai/identity-store.js";
15
16
  import { runDevTestBotSession } from "../agents/devtestbot/tool.js";
@@ -67,6 +68,26 @@ function parsePlannerJson(text) {
67
68
  }
68
69
  }
69
70
 
71
+ // Derive a stable repo key for the managed-proxy DD entitlement gate (#94651):
72
+ // owner/repo from the git remote when present, else a non-identifying local key
73
+ // (hash of the absolute repo root — no raw path leak). Fail-safe to the local key.
74
+ function deriveRepoKey(rootPath) {
75
+ try {
76
+ const remote = spawnSync("git", ["-C", rootPath || ".", "remote", "get-url", "origin"], {
77
+ encoding: "utf-8",
78
+ });
79
+ if (remote.status === 0) {
80
+ const url = String(remote.stdout || "").trim();
81
+ const match = url.match(/[:/]([^/:]+\/[^/]+?)(?:\.git)?$/);
82
+ if (match && match[1]) return match[1].toLowerCase();
83
+ }
84
+ } catch {
85
+ // fall through to the local key
86
+ }
87
+ const abs = path.resolve(rootPath || ".");
88
+ return `local/${crypto.createHash("sha256").update(abs).digest("hex").slice(0, 12)}`;
89
+ }
90
+
70
91
  function buildPlannerPrompt({ rootPath, files = [], findings = [], budget = {} }) {
71
92
  const severityCounts = {};
72
93
  for (const finding of findings || []) {
@@ -86,9 +107,22 @@ function buildPlannerPrompt({ rootPath, files = [], findings = [], budget = {} }
86
107
  ].join("\n");
87
108
  }
88
109
 
89
- async function callPlannerClient({ plannerClient, rootPath, files, findings, budget, sessionUsage }) {
110
+ export async function callPlannerClient({ plannerClient, rootPath, files, findings, budget, sessionUsage }) {
90
111
  if (!plannerClient) return { planned: {}, usageLedger: null };
91
112
  const prompt = buildPlannerPrompt({ rootPath, files, findings, budget });
113
+ // DD entitlement gate metadata (#94651): only label proxy calls when there
114
+ // is a real session usage anchor. API-side DD actions are fail-closed and
115
+ // reject incomplete session context before provider spend.
116
+ const sessionId = String(sessionUsage?.sessionId || "").trim();
117
+ const ddProxyFields = sessionId
118
+ ? {
119
+ action: INVESTOR_DD_USAGE_ACTIONS.devTestBotPlanner,
120
+ sessionId,
121
+ agentId: "investor-dd-devtestbot-planner",
122
+ usageIdempotencyKey: `investor-dd-devtestbot-${crypto.randomUUID()}`,
123
+ metadata: { repoKey: deriveRepoKey(rootPath), phase: "devtestbot" },
124
+ }
125
+ : {};
92
126
  if (typeof plannerClient.decideDevTestBotPhase === "function") {
93
127
  return {
94
128
  planned: await plannerClient.decideDevTestBotPhase({ rootPath, files, findings, budget, prompt }),
@@ -105,7 +139,7 @@ async function callPlannerClient({ plannerClient, rootPath, files, findings, bud
105
139
  }
106
140
  const startedAtIso = new Date().toISOString();
107
141
  if (typeof plannerClient.invoke === "function") {
108
- const response = await plannerClient.invoke({ prompt, stream: false });
142
+ const response = await plannerClient.invoke({ prompt, stream: false, ...ddProxyFields });
109
143
  const usageLedger = sessionUsage
110
144
  ? await recordInvestorDdLlmUsage({
111
145
  usageContext: sessionUsage,
@@ -128,6 +162,7 @@ async function callPlannerClient({ plannerClient, rootPath, files, findings, bud
128
162
  if (typeof plannerClient.generatePlan === "function") {
129
163
  const response = await plannerClient.generatePlan([{ role: "user", content: prompt }], {
130
164
  phase: "devtestbot",
165
+ ...ddProxyFields,
131
166
  });
132
167
  const usageLedger = sessionUsage
133
168
  ? await recordInvestorDdLlmUsage({