principles-disciple 1.168.0 → 1.170.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.
@@ -13,6 +13,10 @@ export interface BehaviorExamplePackAssemblerInput {
13
13
  readonly sourcePainId: string;
14
14
  /** Owner's natural-language desired outcome — must be non-empty. */
15
15
  readonly ownerDesiredOutcome: string;
16
+ /** Owner-selected call that demonstrates behaviour to block. */
17
+ readonly sourceNegativeToolCallId: number;
18
+ /** Owner-selected calls that demonstrate behaviour to allow (1..3). */
19
+ readonly positiveToolCallIds: readonly number[];
16
20
  /**
17
21
  * Project directory, used to detect absolute paths that should be redacted
18
22
  * to relative/basename form in the assembled pack.
@@ -28,16 +32,7 @@ export interface BehaviorExamplePackAssemblerInput {
28
32
  export declare class BehaviorExamplePackAssembler {
29
33
  private readonly db;
30
34
  constructor(opts: BehaviorExamplePackAssemblerOptions);
31
- /**
32
- * Assemble a BehaviorExamplePack from the pain lineage anchored at
33
- * `input.sourcePainId`.
34
- *
35
- * Fail-loud contract (spec §7.2, ERR-069):
36
- * - pain event not found → throws
37
- * - empty trajectory → throws
38
- * - no failing tool call to anchor sourceNegativeCase → throws
39
- * - no successful tool call for positiveCounterexamples → throws
40
- * - assembled pack fails validateBehaviorExamplePack → throws
41
- */
35
+ /** Assemble only from explicit Owner labels; execution outcome is not a label. */
42
36
  assemble(input: BehaviorExamplePackAssemblerInput): BehaviorExamplePack;
37
+ private buildSelectedCase;
43
38
  }
@@ -19,8 +19,9 @@
19
19
  * Boundary: this module is I/O. Pure logic (type + validator) lives in
20
20
  * `@principles/core/runtime-v2` → `behavior-example-pack.ts`.
21
21
  */
22
- import { validateBehaviorExamplePack, } from '@principles/core/runtime-v2';
22
+ import { buildRuleHostAction, computeBehaviorFacts, validateBehaviorExamplePack, } from '@principles/core/runtime-v2';
23
23
  import { TrajectoryRegistry } from './trajectory.js';
24
+ import { assembleHistoryFromRows } from './rule-context-assembler.js';
24
25
  // ── internal constants ─────────────────────────────────────────────────────
25
26
  const MAX_POSITIVES = 3;
26
27
  const MAX_EVIDENCE_REFS = 5;
@@ -52,98 +53,29 @@ export class BehaviorExamplePackAssembler {
52
53
  // owns dispose.
53
54
  this.db = TrajectoryRegistry.get(opts.workspaceDir);
54
55
  }
55
- /**
56
- * Assemble a BehaviorExamplePack from the pain lineage anchored at
57
- * `input.sourcePainId`.
58
- *
59
- * Fail-loud contract (spec §7.2, ERR-069):
60
- * - pain event not found → throws
61
- * - empty trajectory → throws
62
- * - no failing tool call to anchor sourceNegativeCase → throws
63
- * - no successful tool call for positiveCounterexamples → throws
64
- * - assembled pack fails validateBehaviorExamplePack → throws
65
- */
56
+ /** Assemble only from explicit Owner labels; execution outcome is not a label. */
66
57
  assemble(input) {
67
- // ── 1. Look up the anchoring pain event ──
68
58
  const pain = this.db.getPainEventByCanonicalId(input.sourcePainId);
69
59
  if (!pain) {
70
60
  throw new Error(`[BehaviorExamplePackAssembler] pain event not found for canonical_pain_id="${input.sourcePainId}" (ERR-069 fail loud)`);
71
61
  }
72
- // ── 2. Pull the recent trajectory for the pain's session ──
73
- // getRuleHostContextRows returns rows WITH params_json (which
74
- // listToolCallsForSession does not), so we use it here.
75
- const ctxResult = this.db.getRuleHostContextRows(pain.sessionId, HISTORY_LIMIT);
76
- const rows = ctxResult.rows;
77
- if (rows.length === 0) {
78
- throw new Error(`[BehaviorExamplePackAssembler] empty trajectory (no tool calls) for sessionId="${pain.sessionId}" (pain=${input.sourcePainId}, ERR-069 fail loud)`);
79
- }
80
- // ── 3. Partition into failures (negative candidates) and successes (positives) ──
81
- const failures = [];
82
- const successes = [];
83
- for (const row of rows) {
84
- if (row.outcome === 'failure') {
85
- failures.push(row);
86
- }
87
- else if (row.outcome === 'success') {
88
- successes.push(row);
62
+ validateOwnerSelection(input);
63
+ const negativeRow = requireSelectedRow(this.db.getRuleHostEvidenceRow(input.sourceNegativeToolCallId), input.sourceNegativeToolCallId);
64
+ const positiveRows = input.positiveToolCallIds.map((id) => requireSelectedRow(this.db.getRuleHostEvidenceRow(id), id));
65
+ for (const row of [negativeRow, ...positiveRows]) {
66
+ if (row.sessionId !== pain.sessionId) {
67
+ throw new Error(`[BehaviorExamplePackAssembler] selected tool call ${row.id} session mismatch; all examples must belong to pain session ${pain.sessionId}`);
89
68
  }
90
- // 'blocked' rows are not used as either positive or negative cases.
91
- }
92
- if (failures.length === 0) {
93
- throw new Error(`[BehaviorExamplePackAssembler] no failing tool call in sessionId="${pain.sessionId}" — cannot build sourceNegativeCase (pain=${input.sourcePainId}, ERR-069 fail loud)`);
94
- }
95
- if (successes.length === 0) {
96
- throw new Error(`[BehaviorExamplePackAssembler] no successful tool call in sessionId="${pain.sessionId}" — cannot build positiveCounterexamples (pain=${input.sourcePainId}, ERR-069 fail loud)`);
97
- }
98
- // ── 4. Build sourceNegativeCase from the most recent failure ──
99
- const sourceFailure = failures[failures.length - 1];
100
- if (!sourceFailure) {
101
- // Defensive: should be unreachable due to the failures.length === 0 check
102
- // above, but noUncheckedIndexedAccess requires the guard.
103
- throw new Error(`[BehaviorExamplePackAssembler] internal error: sourceFailure undefined (pain=${input.sourcePainId})`);
104
- }
105
- const sourceNegativeCase = buildCaseFromRow(sourceFailure, 'negative', 'block', input.projectDir);
106
- // ── 5. Build positiveCounterexamples from successes (≤3, most recent first) ──
107
- const positiveRows = successes.slice(-MAX_POSITIVES); // most recent ≤3
108
- const positiveCounterexamples = [];
109
- for (const row of positiveRows) {
110
- positiveCounterexamples.push(buildCaseFromRow(row, 'positive', 'allow', input.projectDir));
111
- }
112
- // ── 6. Build evidenceRefs from pain events + gate blocks (≤5) ──
113
- const evidenceRefs = [];
114
- const sessionPainEvents = this.db.listPainEventsForSession(pain.sessionId);
115
- for (const p of sessionPainEvents) {
116
- if (evidenceRefs.length >= MAX_EVIDENCE_REFS)
117
- break;
118
- evidenceRefs.push(`pain:${p.id}`);
119
- }
120
- const gateBlocks = this.db.listGateBlocksForSession(pain.sessionId);
121
- for (const g of gateBlocks) {
122
- if (evidenceRefs.length >= MAX_EVIDENCE_REFS)
123
- break;
124
- evidenceRefs.push(`gate:${g.id}`);
125
69
  }
126
- if (evidenceRefs.length === 0) {
127
- // The anchor pain event itself always provides at least one evidenceRef.
128
- evidenceRefs.push(`pain:${pain.id}`);
129
- }
130
- // ── 7. Apply redaction (spec §7.2: "经过脱敏") ──
131
- const redactionResult = redactParams(sourceNegativeCase.params, input.projectDir);
132
- const redactedNegativeCase = {
133
- ...sourceNegativeCase,
134
- params: redactionResult.redacted,
135
- };
136
- const redactedPositives = positiveCounterexamples.map((c) => {
137
- const r = redactParams(c.params, input.projectDir);
138
- return { ...c, params: r.redacted };
139
- });
140
- const redactionNotes = [...redactionResult.notes];
141
- // ── 8. Assemble the pack ──
70
+ const redactionNotes = [];
71
+ const buildCtx = { projectDir: input.projectDir, redactionNotes };
72
+ const sourceNegativeCase = this.buildSelectedCase(negativeRow, 'negative', 'block', buildCtx);
73
+ const positiveCounterexamples = positiveRows.map((row) => this.buildSelectedCase(row, 'positive', 'allow', buildCtx));
142
74
  const pack = {
143
- sourceNegativeCase: redactedNegativeCase,
75
+ sourceNegativeCase,
144
76
  ownerDesiredOutcome: input.ownerDesiredOutcome,
145
- positiveCounterexamples: redactedPositives,
146
- evidenceRefs,
77
+ positiveCounterexamples,
78
+ evidenceRefs: [`pain:${pain.id}`, `tool_call:${negativeRow.id}`, ...positiveRows.map((row) => `tool_call:${row.id}`)].slice(0, MAX_EVIDENCE_REFS),
147
79
  redactionNotes,
148
80
  };
149
81
  // ── 9. Validate — fail loud (ERR-069) ──
@@ -153,6 +85,34 @@ export class BehaviorExamplePackAssembler {
153
85
  }
154
86
  return pack;
155
87
  }
88
+ buildSelectedCase(row, kind, expectedDecision, ctx) {
89
+ const { projectDir, redactionNotes } = ctx;
90
+ const base = buildCaseFromRow(row, kind, expectedDecision, projectDir);
91
+ const redaction = redactParams(base.params, projectDir);
92
+ redactionNotes.push(...redaction.notes);
93
+ const historyResult = this.db.getRuleHostContextRowsBefore(row.sessionId, row.id, HISTORY_LIMIT);
94
+ const history = assembleHistoryFromRows(historyResult.rows, historyResult.truncated, projectDir);
95
+ const action = buildRuleHostAction(row.toolName, redaction.redacted, projectDir);
96
+ const ruleContext = { version: 2, history, facts: computeBehaviorFacts(history, action.normalizedPath || null, null) };
97
+ return { ...base, params: redaction.redacted, ruleContext };
98
+ }
99
+ }
100
+ function validateOwnerSelection(input) {
101
+ if (!Number.isSafeInteger(input.sourceNegativeToolCallId) || input.sourceNegativeToolCallId <= 0) {
102
+ throw new Error('[BehaviorExamplePackAssembler] sourceNegativeToolCallId must be a positive integer');
103
+ }
104
+ if (!Array.isArray(input.positiveToolCallIds) || input.positiveToolCallIds.length === 0 || input.positiveToolCallIds.length > MAX_POSITIVES) {
105
+ throw new Error('[BehaviorExamplePackAssembler] positiveToolCallIds must contain 1 to at most 3 IDs');
106
+ }
107
+ const allIds = [input.sourceNegativeToolCallId, ...input.positiveToolCallIds];
108
+ if (allIds.some((id) => !Number.isSafeInteger(id) || id <= 0) || new Set(allIds).size !== allIds.length) {
109
+ throw new Error('[BehaviorExamplePackAssembler] selected tool call IDs must be unique positive integers');
110
+ }
111
+ }
112
+ function requireSelectedRow(row, id) {
113
+ if (!row)
114
+ throw new Error(`[BehaviorExamplePackAssembler] selected tool call id=${id} not found`);
115
+ return row;
156
116
  }
157
117
  // ── internal: case builder ─────────────────────────────────────────────────
158
118
  /**
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import * as fs from 'fs';
16
16
  import * as path from 'path';
17
- import yaml from 'js-yaml';
17
+ import * as yaml from 'js-yaml';
18
18
  import { validatePdConfig, computeEffectivePdConfig, computeFeatureFlagsFromConfig, INTERNAL_AGENT_NAMES, } from '@principles/core/runtime-v2';
19
19
  // ── Constants ────────────────────────────────────────────────────────────────
20
20
  export const PD_CONFIG_DIR = '.pd';
@@ -232,6 +232,10 @@ export interface RuleHostContextRow {
232
232
  readonly outcome: string;
233
233
  readonly paramsJson: string;
234
234
  }
235
+ /** A single tool call selected by an Owner as labelled RuleHost evidence. */
236
+ export interface RuleHostEvidenceRow extends RuleHostContextRow {
237
+ readonly sessionId: string;
238
+ }
235
239
  /**
236
240
  * Result of getRuleHostContextRows: FIFO-ordered rows + truncated flag.
237
241
  * truncated=true when more than `limit` rows existed (limit+1 read trick).
@@ -1,5 +1,5 @@
1
- import type { CorrectionSampleReviewStatus, CorrectionExportMode, TrajectoryDataStats, TrajectoryAssistantTurnInput, TrajectoryUserTurnInput, TrajectoryToolCallInput, TrajectoryPainEventInput, TrajectoryGateBlockInput, TrajectoryTrustChangeInput, TrajectoryPrincipleEventInput, TrajectoryTaskOutcomeInput, TrajectorySessionInput, EvolutionTaskInput, EvolutionEventInput, EvolutionTaskRecord, EvolutionEventRecord, EvolutionTaskFilters, AssistantTurnRecord, CorrectionSampleRecord, TrajectoryExportResult, TrajectoryDatabaseOptions, RuleHostContextResult } from './trajectory-types.js';
2
- export type { CorrectionSampleReviewStatus, CorrectionExportMode, TrajectoryDataStats, TrajectoryAssistantTurnInput, TrajectoryUserTurnInput, TrajectoryToolCallInput, TrajectoryPainEventInput, TrajectoryGateBlockInput, DailyMetricRow, TrajectoryTrustChangeInput, TrajectoryPrincipleEventInput, TrajectoryTaskOutcomeInput, TrajectorySessionInput, TaskKind, TaskPriority, EvolutionTaskInput, EvolutionEventInput, EvolutionTaskRecord, EvolutionEventRecord, EvolutionTaskFilters, AssistantTurnRecord, CorrectionSampleRecord, TrajectoryExportResult, TrajectoryDatabaseOptions, RuleHostContextRow, RuleHostContextResult, } from './trajectory-types.js';
1
+ import type { CorrectionSampleReviewStatus, CorrectionExportMode, TrajectoryDataStats, TrajectoryAssistantTurnInput, TrajectoryUserTurnInput, TrajectoryToolCallInput, TrajectoryPainEventInput, TrajectoryGateBlockInput, TrajectoryTrustChangeInput, TrajectoryPrincipleEventInput, TrajectoryTaskOutcomeInput, TrajectorySessionInput, EvolutionTaskInput, EvolutionEventInput, EvolutionTaskRecord, EvolutionEventRecord, EvolutionTaskFilters, AssistantTurnRecord, CorrectionSampleRecord, TrajectoryExportResult, TrajectoryDatabaseOptions, RuleHostContextResult, RuleHostEvidenceRow } from './trajectory-types.js';
2
+ export type { CorrectionSampleReviewStatus, CorrectionExportMode, TrajectoryDataStats, TrajectoryAssistantTurnInput, TrajectoryUserTurnInput, TrajectoryToolCallInput, TrajectoryPainEventInput, TrajectoryGateBlockInput, DailyMetricRow, TrajectoryTrustChangeInput, TrajectoryPrincipleEventInput, TrajectoryTaskOutcomeInput, TrajectorySessionInput, TaskKind, TaskPriority, EvolutionTaskInput, EvolutionEventInput, EvolutionTaskRecord, EvolutionEventRecord, EvolutionTaskFilters, AssistantTurnRecord, CorrectionSampleRecord, TrajectoryExportResult, TrajectoryDatabaseOptions, RuleHostContextRow, RuleHostContextResult, RuleHostEvidenceRow, } from './trajectory-types.js';
3
3
  /**
4
4
  * Initialize trajectory.db schema at the given workspace directory.
5
5
  *
@@ -130,6 +130,10 @@ export declare class TrajectoryDatabase {
130
130
  * Spec: §5.1, §5.2. ERR-026: reuses production schema (no hand-written DDL).
131
131
  */
132
132
  getRuleHostContextRows(sessionId: string, limit?: number): RuleHostContextResult;
133
+ /** Resolve one Owner-selected tool call without inferring its desired label. */
134
+ getRuleHostEvidenceRow(id: number): RuleHostEvidenceRow | null;
135
+ /** Return FIFO history strictly before an Owner-selected tool call. */
136
+ getRuleHostContextRowsBefore(sessionId: string, beforeId: number, limit?: number): RuleHostContextResult;
133
137
  /**
134
138
  * List pain events for a session.
135
139
  *
@@ -961,6 +961,47 @@ export class TrajectoryDatabase {
961
961
  truncated,
962
962
  };
963
963
  }
964
+ /** Resolve one Owner-selected tool call without inferring its desired label. */
965
+ getRuleHostEvidenceRow(id) {
966
+ const row = this.db.prepare(`
967
+ SELECT id, session_id, tool_name, outcome, params_json
968
+ FROM tool_calls
969
+ WHERE id = ?
970
+ LIMIT 1
971
+ `).get(id);
972
+ if (!isStringRecord(row))
973
+ return null;
974
+ if (typeof row.id !== 'number' || typeof row.session_id !== 'string')
975
+ return null;
976
+ if (typeof row.tool_name !== 'string' || typeof row.outcome !== 'string' || typeof row.params_json !== 'string')
977
+ return null;
978
+ return {
979
+ id: row.id,
980
+ sessionId: row.session_id,
981
+ toolName: row.tool_name,
982
+ outcome: row.outcome,
983
+ paramsJson: row.params_json,
984
+ };
985
+ }
986
+ /** Return FIFO history strictly before an Owner-selected tool call. */
987
+ getRuleHostContextRowsBefore(sessionId, beforeId, limit = 20) {
988
+ const cappedLimit = Math.max(1, Math.floor(limit));
989
+ const rows = this.db.prepare(`
990
+ SELECT id, tool_name, outcome, params_json
991
+ FROM tool_calls
992
+ WHERE session_id = ? AND id < ?
993
+ ORDER BY id DESC
994
+ LIMIT ?
995
+ `).all(sessionId, beforeId, cappedLimit + 1);
996
+ const truncated = rows.length > cappedLimit;
997
+ const output = (truncated ? rows.slice(0, cappedLimit) : rows).reverse();
998
+ return {
999
+ rows: output.map((row) => ({
1000
+ id: Number(row.id), toolName: String(row.tool_name), outcome: String(row.outcome), paramsJson: String(row.params_json),
1001
+ })),
1002
+ truncated,
1003
+ };
1004
+ }
964
1005
  /**
965
1006
  * List pain events for a session.
966
1007
  *
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
- import yaml from 'js-yaml';
12
+ import * as yaml from 'js-yaml';
13
13
  // ─────────────────────────────────────────────────────────────────────────────
14
14
  // WorkflowFunnelLoader
15
15
  // ─────────────────────────────────────────────────────────────────────────────
@@ -33,7 +33,7 @@ export class WorkflowFunnelLoader {
33
33
  }
34
34
  try {
35
35
  const content = fs.readFileSync(this.configPath, 'utf-8');
36
- const config = yaml.load(content, { schema: yaml.DEFAULT_SCHEMA });
36
+ const config = yaml.load(content, { schema: yaml.CORE_SCHEMA });
37
37
  if (!config || typeof config.version !== 'string' || !Array.isArray(config.funnels)) {
38
38
  const msg = 'workflows.yaml validation failed: missing version or funnels array. Preserving last valid config.';
39
39
  console.warn(`[WorkflowFunnelLoader] ${msg}`);
@@ -30,7 +30,13 @@ export function handleBeforeToolCall(event, ctx) {
30
30
  const wctx = WorkspaceContext.fromHookContext(ctx);
31
31
  // 2. Use the same action builder as Golden Trace replay. This is the single
32
32
  // path extraction + normalization contract for production and evaluation.
33
- const action = buildRuleHostAction(event.toolName, event.params ?? {}, ctx.workspaceDir, {
33
+ // CodeRabbit PR2 Comment 1: pass the normalized workspace root from
34
+ // WorkspaceContext (wctx.workspaceDir) rather than the raw ctx.workspaceDir,
35
+ // so action.normalizedPath is consistent with the rest of the hook path
36
+ // (which uses WorkspaceContext.fromHookContext's normalized root). Mixing
37
+ // the raw value here produced paths that disagreed with the normalized root
38
+ // used downstream by the rule host / rule-context assembler.
39
+ const action = buildRuleHostAction(event.toolName, event.params ?? {}, wctx.workspaceDir, {
34
40
  isBashTool: isBash,
35
41
  isWriteTool,
36
42
  });
@@ -0,0 +1,4 @@
1
+ /** Side-effect-free public boundary for RuleHost evidence assembly. */
2
+ export { BehaviorExamplePackAssembler } from './core/behavior-example-pack-assembler.js';
3
+ export type { BehaviorExamplePackAssemblerInput, BehaviorExamplePackAssemblerOptions, } from './core/behavior-example-pack-assembler.js';
4
+ export { TrajectoryRegistry as RuleHostEvidenceRegistry } from './core/trajectory.js';
@@ -0,0 +1,3 @@
1
+ /** Side-effect-free public boundary for RuleHost evidence assembly. */
2
+ export { BehaviorExamplePackAssembler } from './core/behavior-example-pack-assembler.js';
3
+ export { TrajectoryRegistry as RuleHostEvidenceRegistry } from './core/trajectory.js';
@@ -2,7 +2,7 @@
2
2
  "id": "principles-disciple",
3
3
  "name": "Principles Disciple",
4
4
  "description": "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
5
- "version": "1.168.0",
5
+ "version": "1.170.0",
6
6
  "activation": {
7
7
  "onCapabilities": [
8
8
  "hook"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.168.0",
3
+ "version": "1.170.0",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -9,6 +9,10 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
+ },
13
+ "./rulehost-evidence": {
14
+ "types": "./dist/rulehost-evidence.d.ts",
15
+ "default": "./dist/rulehost-evidence.js"
12
16
  }
13
17
  },
14
18
  "repository": {
@@ -83,7 +87,7 @@
83
87
  "@sinclair/typebox": "^0.34.48",
84
88
  "better-sqlite3": "^12.9.0",
85
89
  "commander": "^12.0.0",
86
- "js-yaml": "^4.1.1",
90
+ "js-yaml": "^5.2.0",
87
91
  "micromatch": "^4.0.8"
88
92
  }
89
93
  }