@principles/workbuddy-adapter 0.2.3 → 0.2.4

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 (2) hide show
  1. package/dist/pd-hook.js +73 -2
  2. package/package.json +3 -3
package/dist/pd-hook.js CHANGED
@@ -37,7 +37,7 @@ import path from 'node:path';
37
37
  import process from 'node:process';
38
38
  import { pathToFileURL, fileURLToPath } from 'node:url';
39
39
  import { appendEventLogLine, redactTelemetryString, isRuleHostEvaluatedEventData } from '@principles/core/runtime-v2';
40
- import { createProductionHostRuntime, loadPdConfigForPlugin, parseOwnerStatementPrefix } from '@principles/host-runtime';
40
+ import { createProductionHostRuntime, loadPdConfigForPlugin, parseOwnerStatementPrefix, assessWorkbuddyLiveKeyword, admitTrustedWorkbuddyLiveCorrection, ensureGovernanceDiagnosticianTask } from '@principles/host-runtime';
41
41
  import { computeFeatureFlagsFromConfig } from '@principles/core/runtime-v2';
42
42
  import { WorkBuddyHooksHostAdapter } from './host-adapter.js';
43
43
  import { WORKBUDDY_TOOL_SEMANTICS } from './tool-semantics.js';
@@ -255,6 +255,76 @@ export async function processWorkbuddyHookInvocation(rawStdin, channelEventName,
255
255
  const ownerReceipt = channelEventName === 'UserPromptSubmit'
256
256
  ? await recordWorkbuddyOwnerStatement(event, resolution.workspaceDir)
257
257
  : undefined;
258
+ // SPEC-PD-CROSS-HOST-KEYWORD-PAIN-001 P0: only a REAL current-user
259
+ // UserPromptSubmit may enter this quiet, default-OFF live keyword path.
260
+ // It does not inspect transcripts or alter normal hook stdout / exit codes.
261
+ const autoDiagnostics = [];
262
+ if (channelEventName === 'UserPromptSubmit' &&
263
+ event.kind === 'before_prompt_build' &&
264
+ flags.workbuddy_live_keyword_pain?.enabled === true) {
265
+ try {
266
+ const raw = parsed;
267
+ const prompt = event.context.promptContent;
268
+ const fromOwner = Object.getOwnPropertyDescriptor(raw, 'is_internal_continuation')?.value === false;
269
+ const nativeId = event.context.turnId;
270
+ const explicitReserved = typeof prompt === 'string' && prompt.trimStart().startsWith('PD记录痛苦');
271
+ if (fromOwner && nativeId !== undefined && nativeId.trim().length > 0 &&
272
+ typeof prompt === 'string' && !explicitReserved) {
273
+ const matched = assessWorkbuddyLiveKeyword(prompt, event.context.sessionId);
274
+ if (matched.matched) {
275
+ const admitted = admitTrustedWorkbuddyLiveCorrection({
276
+ workspaceDir: resolution.workspaceDir,
277
+ rawEvent: parsed,
278
+ });
279
+ if (!admitted.ok) {
280
+ autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_admission_' + admitted.reason, admitted.nextAction));
281
+ }
282
+ else {
283
+ for (const outcome of admitted.outcomes) {
284
+ if (!('disposition' in outcome)) {
285
+ autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_admission_rejected', outcome.reason));
286
+ continue;
287
+ }
288
+ if (outcome.disposition === 'admitted' || outcome.disposition === 'already_admitted') {
289
+ const ensured = await ensureGovernanceDiagnosticianTask({
290
+ workspaceDir: resolution.workspaceDir,
291
+ logicalObservationKey: outcome.logicalObservationKey,
292
+ canonicalPainId: outcome.canonicalPainId,
293
+ });
294
+ if (!ensured.ok) {
295
+ autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_task_' + ensured.reason, ensured.nextAction));
296
+ }
297
+ else {
298
+ // Metadata only: never log the raw prompt or fabricate a
299
+ // completed diagnosis, approval or activation.
300
+ try {
301
+ appendEventLogLine(path.join(resolution.workspaceDir, '.state'), {
302
+ ts: new Date().toISOString(),
303
+ type: 'workbuddy_keyword_pain_admitted',
304
+ category: 'detected',
305
+ sessionId: event.context.sessionId,
306
+ data: { hostKind: 'workbuddy', outcome: outcome.disposition,
307
+ painId: outcome.canonicalPainId, taskId: ensured.taskId },
308
+ });
309
+ }
310
+ catch { /* Evidence-log failure must not block the prompt. */ }
311
+ }
312
+ }
313
+ else if (outcome.disposition === 'rate_limited') {
314
+ autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_rate_limited', 'Per-session strong signal limit reached; no new Pain was created.'));
315
+ }
316
+ }
317
+ }
318
+ }
319
+ else if (matched.reason === 'prompt_too_large') {
320
+ autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_prompt_too_large', 'Oversized prompt was not scanned or persisted as a keyword signal.'));
321
+ }
322
+ }
323
+ }
324
+ catch (error) {
325
+ autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_unexpected:' + errorMessage(error), 'The current prompt was preserved; inspect the governance workspace.'));
326
+ }
327
+ }
258
328
  const receiptOutput = (output) => ownerReceipt === undefined ? output : {
259
329
  hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: [ownerReceipt, output?.hookSpecificOutput.additionalContext].filter(Boolean).join('\n\n') },
260
330
  };
@@ -360,6 +430,7 @@ export async function processWorkbuddyHookInvocation(rawStdin, channelEventName,
360
430
  ...annotateContextWarnings(result.warnings ?? []).slice(0, 16).map((warning) => diagnostic('degraded', warning, 'Inspect the PD governance workspace state and retry; the hook stayed permissive (allow+warnings is degraded health, not fail-closed).')),
361
431
  ...evaluationDiagnostics,
362
432
  ...evidenceDiagnostics,
433
+ ...autoDiagnostics,
363
434
  ];
364
435
  return { stdout: receiptOutput(encoded), exitCode: 0, stderr: [...stderr, ...(ownerReceipt === undefined ? [] : [ownerReceipt])] };
365
436
  }
@@ -373,7 +444,7 @@ export async function processWorkbuddyHookInvocation(rawStdin, channelEventName,
373
444
  }
374
445
  // UserPromptSubmit: protect the prompt (exit 2 clears it — P02).
375
446
  // after_tool_call: the tool already ran — report evidence degradation only.
376
- return { stdout: receiptOutput(null), exitCode: 0, stderr: [diagnostic('degraded', reason, nextAction), ...(ownerReceipt === undefined ? [] : [ownerReceipt])] };
447
+ return { stdout: receiptOutput(null), exitCode: 0, stderr: [diagnostic('degraded', reason, nextAction), ...autoDiagnostics, ...(ownerReceipt === undefined ? [] : [ownerReceipt])] };
377
448
  }
378
449
  }
379
450
  // ─── Process-boundary belt (PRI-943 pattern, WorkBuddy exit-code semantics) ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/workbuddy-adapter",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "WorkBuddy host adapter for Principles Disciple — implements the HostAdapter interface for WorkBuddy's Git Bash command-hook model (ADR-0020 third host, SPEC workbuddy-host-adapter v0.3.0).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,8 +42,8 @@
42
42
  "lint": "eslint \"src/**/*.ts\""
43
43
  },
44
44
  "dependencies": {
45
- "@principles/core": "^1.293.4",
46
- "@principles/host-runtime": "^0.10.2",
45
+ "@principles/core": "^1.294.1",
46
+ "@principles/host-runtime": "^0.10.4",
47
47
  "@principles/install-layout": "^0.4.0"
48
48
  },
49
49
  "devDependencies": {