principles-disciple 1.157.0 → 1.159.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.
- package/dist/core/rule-context-assembler.d.ts +45 -0
- package/dist/core/rule-context-assembler.js +148 -0
- package/dist/core/trajectory-types.d.ts +18 -0
- package/dist/core/trajectory.d.ts +11 -2
- package/dist/core/trajectory.js +30 -0
- package/dist/hooks/gate.js +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/core/risk-calculator.d.ts +0 -22
- package/dist/core/risk-calculator.js +0 -88
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRI-482 Phase 3 — Production RuleContext v2 assembler
|
|
3
|
+
*
|
|
4
|
+
* Converts raw TrajectoryDatabase rows into a validated RuleHistoryWindow,
|
|
5
|
+
* and provides buildProductionRuleContext for gate integration.
|
|
6
|
+
*
|
|
7
|
+
* ERR prevention:
|
|
8
|
+
* - ERR-001: every row field is validated as unknown. No `as` bypass on
|
|
9
|
+
* parsed params_json or outcome.
|
|
10
|
+
* - ERR-024: buildProductionRuleContext fail-soft — query failure returns
|
|
11
|
+
* unavailable, never throws.
|
|
12
|
+
* - ERR-025: callers use recordToolCall → getRuleHostContextRows → this
|
|
13
|
+
* assembler, covering the full production chain.
|
|
14
|
+
*
|
|
15
|
+
* Spec: docs/superpowers/specs/2026-06-27-rulecode-context-vision-design.md §5.
|
|
16
|
+
*/
|
|
17
|
+
import type { RuleContextV2, RuleHistoryWindow } from '@principles/core/runtime-v2';
|
|
18
|
+
import type { RuleHostContextResult, RuleHostContextRow } from './trajectory-types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Minimal interface for the data source that TrajectoryDatabase satisfies.
|
|
21
|
+
* Allows testing buildProductionRuleContext without a real DB.
|
|
22
|
+
*/
|
|
23
|
+
export interface RuleContextDataSource {
|
|
24
|
+
getRuleHostContextRows(sessionId: string, limit: number): RuleHostContextResult;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Convert raw DB rows into a validated RuleHistoryWindow.
|
|
28
|
+
*
|
|
29
|
+
* If ANY row is malformed (bad params_json, invalid outcome, empty tool_name),
|
|
30
|
+
* the entire window is marked unavailable (fail loud, spec §5.2).
|
|
31
|
+
*
|
|
32
|
+
* ERR-001: row fields are validated as unknown. params_json is JSON.parsed
|
|
33
|
+
* then structurally checked (must be a non-array object). No `as` bypass.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assembleHistoryFromRows(rows: readonly RuleHostContextRow[], truncated: boolean, projectDir: string): RuleHistoryWindow;
|
|
36
|
+
/**
|
|
37
|
+
* Build a RuleContextV2 from the production data source (TrajectoryDatabase).
|
|
38
|
+
*
|
|
39
|
+
* ERR-024: if the query or assembly fails, returns an unavailable context
|
|
40
|
+
* (fail-soft). Never throws.
|
|
41
|
+
*
|
|
42
|
+
* sameActionBlockCount is always null in this version (spec §5.4:
|
|
43
|
+
* session-tracker.blockedAttempts is a session total, not per-action).
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildProductionRuleContext(sessionId: string | null | undefined, targetPath: string | null, source: RuleContextDataSource, projectDir: string, limit?: number): RuleContextV2;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PRI-482 Phase 3 — Production RuleContext v2 assembler
|
|
3
|
+
*
|
|
4
|
+
* Converts raw TrajectoryDatabase rows into a validated RuleHistoryWindow,
|
|
5
|
+
* and provides buildProductionRuleContext for gate integration.
|
|
6
|
+
*
|
|
7
|
+
* ERR prevention:
|
|
8
|
+
* - ERR-001: every row field is validated as unknown. No `as` bypass on
|
|
9
|
+
* parsed params_json or outcome.
|
|
10
|
+
* - ERR-024: buildProductionRuleContext fail-soft — query failure returns
|
|
11
|
+
* unavailable, never throws.
|
|
12
|
+
* - ERR-025: callers use recordToolCall → getRuleHostContextRows → this
|
|
13
|
+
* assembler, covering the full production chain.
|
|
14
|
+
*
|
|
15
|
+
* Spec: docs/superpowers/specs/2026-06-27-rulecode-context-vision-design.md §5.
|
|
16
|
+
*/
|
|
17
|
+
import { canonicalizeToolKind, computeBehaviorFacts, extractFilePathFromParams, normalizePathPure, UNAVAILABLE_RULE_CONTEXT, } from '@principles/core/runtime-v2';
|
|
18
|
+
// ── internal constants ─────────────────────────────────────────────────────
|
|
19
|
+
const VALID_OUTCOMES = new Set([
|
|
20
|
+
'success',
|
|
21
|
+
'failure',
|
|
22
|
+
'blocked',
|
|
23
|
+
]);
|
|
24
|
+
const DEFAULT_HISTORY_LIMIT = 20;
|
|
25
|
+
// ── type guards (rc-2: no `as` bypass) ─────────────────────────────────────
|
|
26
|
+
function isPlainObject(value) {
|
|
27
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
function isRuleToolOutcome(value) {
|
|
30
|
+
return typeof value === 'string' && VALID_OUTCOMES.has(value);
|
|
31
|
+
}
|
|
32
|
+
// ── assembleHistoryFromRows ────────────────────────────────────────────────
|
|
33
|
+
/**
|
|
34
|
+
* Convert raw DB rows into a validated RuleHistoryWindow.
|
|
35
|
+
*
|
|
36
|
+
* If ANY row is malformed (bad params_json, invalid outcome, empty tool_name),
|
|
37
|
+
* the entire window is marked unavailable (fail loud, spec §5.2).
|
|
38
|
+
*
|
|
39
|
+
* ERR-001: row fields are validated as unknown. params_json is JSON.parsed
|
|
40
|
+
* then structurally checked (must be a non-array object). No `as` bypass.
|
|
41
|
+
*/
|
|
42
|
+
export function assembleHistoryFromRows(rows, truncated, projectDir) {
|
|
43
|
+
const records = [];
|
|
44
|
+
for (const row of rows) {
|
|
45
|
+
// ── validate tool_name (ERR-001: unknown) ──
|
|
46
|
+
const toolName = row.toolName;
|
|
47
|
+
if (typeof toolName !== 'string' || toolName.length === 0) {
|
|
48
|
+
return unavailable(`row id=${row.id}: tool_name must be a non-empty string`);
|
|
49
|
+
}
|
|
50
|
+
// ── validate outcome (ERR-001: unknown, enum check) ──
|
|
51
|
+
const outcome = row.outcome;
|
|
52
|
+
if (!isRuleToolOutcome(outcome)) {
|
|
53
|
+
return unavailable(`row id=${row.id}: outcome "${row.outcome}" is not a valid RuleToolOutcome`);
|
|
54
|
+
}
|
|
55
|
+
// ── validate & parse params_json (ERR-001: unknown) ──
|
|
56
|
+
const paramsJsonStr = row.paramsJson;
|
|
57
|
+
if (typeof paramsJsonStr !== 'string') {
|
|
58
|
+
return unavailable(`row id=${row.id}: params_json must be a string`);
|
|
59
|
+
}
|
|
60
|
+
let parsedParams;
|
|
61
|
+
try {
|
|
62
|
+
parsedParams = JSON.parse(paramsJsonStr);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return unavailable(`row id=${row.id}: params_json is not valid JSON`);
|
|
66
|
+
}
|
|
67
|
+
// Must be a non-array object (spec §5.2)
|
|
68
|
+
if (!isPlainObject(parsedParams)) {
|
|
69
|
+
return unavailable(`row id=${row.id}: params_json must parse to a non-array object`);
|
|
70
|
+
}
|
|
71
|
+
// ── build RuleToolCallRecord ──
|
|
72
|
+
const canonicalKind = canonicalizeToolKind(toolName);
|
|
73
|
+
// Extract normalized path using the same pure logic as buildRuleHostAction
|
|
74
|
+
// (avoids production/replay drift, spec §4.4)
|
|
75
|
+
const rawPath = extractFilePathFromParams(parsedParams, {
|
|
76
|
+
isBashTool: canonicalKind === 'execute',
|
|
77
|
+
isWriteTool: canonicalKind === 'write',
|
|
78
|
+
toolName,
|
|
79
|
+
});
|
|
80
|
+
const normalizedPath = normalizePathPure(rawPath, projectDir);
|
|
81
|
+
records.push({
|
|
82
|
+
sequenceId: row.id,
|
|
83
|
+
toolName,
|
|
84
|
+
canonicalKind,
|
|
85
|
+
normalizedPath: normalizedPath === '' ? null : normalizedPath,
|
|
86
|
+
paramsSummary: parsedParams,
|
|
87
|
+
outcome,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
status: 'available',
|
|
92
|
+
truncated,
|
|
93
|
+
calls: records,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// ── buildProductionRuleContext ─────────────────────────────────────────────
|
|
97
|
+
/**
|
|
98
|
+
* Build a RuleContextV2 from the production data source (TrajectoryDatabase).
|
|
99
|
+
*
|
|
100
|
+
* ERR-024: if the query or assembly fails, returns an unavailable context
|
|
101
|
+
* (fail-soft). Never throws.
|
|
102
|
+
*
|
|
103
|
+
* sameActionBlockCount is always null in this version (spec §5.4:
|
|
104
|
+
* session-tracker.blockedAttempts is a session total, not per-action).
|
|
105
|
+
*/
|
|
106
|
+
export function buildProductionRuleContext(sessionId, targetPath, source, projectDir, limit = DEFAULT_HISTORY_LIMIT) {
|
|
107
|
+
// Guard: no session → no context
|
|
108
|
+
if (!sessionId || typeof sessionId !== 'string' || sessionId.length === 0) {
|
|
109
|
+
return UNAVAILABLE_RULE_CONTEXT;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const result = source.getRuleHostContextRows(sessionId, limit);
|
|
113
|
+
const history = assembleHistoryFromRows(result.rows, result.truncated, projectDir);
|
|
114
|
+
// sameActionBlockCount = null (spec §5.4 — no reliable per-action source)
|
|
115
|
+
const facts = computeBehaviorFacts(history, targetPath, null);
|
|
116
|
+
return {
|
|
117
|
+
version: 2,
|
|
118
|
+
history,
|
|
119
|
+
facts,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// ERR-024: fail-soft — never let a DB error propagate to the rule host
|
|
124
|
+
return unavailableContext('query or assembly failed');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// ── internal helpers ───────────────────────────────────────────────────────
|
|
128
|
+
function unavailable(reason) {
|
|
129
|
+
return {
|
|
130
|
+
status: 'unavailable',
|
|
131
|
+
unavailableReason: reason,
|
|
132
|
+
truncated: false,
|
|
133
|
+
calls: [],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function unavailableContext(reason) {
|
|
137
|
+
return {
|
|
138
|
+
version: 2,
|
|
139
|
+
history: unavailable(reason),
|
|
140
|
+
facts: {
|
|
141
|
+
priorReadOfTarget: 'unknown',
|
|
142
|
+
readCount: null,
|
|
143
|
+
writeCount: null,
|
|
144
|
+
uniqueWritePathCount: null,
|
|
145
|
+
sameActionBlockCount: null,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -222,4 +222,22 @@ export interface TrajectoryDatabaseOptions {
|
|
|
222
222
|
busyTimeoutMs?: number;
|
|
223
223
|
orphanBlobGraceDays?: number;
|
|
224
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Raw row from tool_calls for RuleContext v2 history assembly.
|
|
227
|
+
* Fields are raw strings from SQLite — the assembler validates them as unknown.
|
|
228
|
+
*/
|
|
229
|
+
export interface RuleHostContextRow {
|
|
230
|
+
readonly id: number;
|
|
231
|
+
readonly toolName: string;
|
|
232
|
+
readonly outcome: string;
|
|
233
|
+
readonly paramsJson: string;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Result of getRuleHostContextRows: FIFO-ordered rows + truncated flag.
|
|
237
|
+
* truncated=true when more than `limit` rows existed (limit+1 read trick).
|
|
238
|
+
*/
|
|
239
|
+
export interface RuleHostContextResult {
|
|
240
|
+
readonly rows: readonly RuleHostContextRow[];
|
|
241
|
+
readonly truncated: boolean;
|
|
242
|
+
}
|
|
225
243
|
export type { DailyMetricRow };
|
|
@@ -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 } 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, } 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 } 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';
|
|
3
3
|
/**
|
|
4
4
|
* Initialize trajectory.db schema at the given workspace directory.
|
|
5
5
|
*
|
|
@@ -121,6 +121,15 @@ export declare class TrajectoryDatabase {
|
|
|
121
121
|
resultPreview: string | null;
|
|
122
122
|
createdAt: string;
|
|
123
123
|
}[];
|
|
124
|
+
/**
|
|
125
|
+
* PRI-482 Phase 3: Query tool_calls for RuleContext v2 history assembly.
|
|
126
|
+
*
|
|
127
|
+
* Reads limit+1 rows (DESC by id) to compute truncated, then reverses to FIFO.
|
|
128
|
+
* Returns raw rows — the assembler (rule-context-assembler.ts) validates them.
|
|
129
|
+
*
|
|
130
|
+
* Spec: §5.1, §5.2. ERR-026: reuses production schema (no hand-written DDL).
|
|
131
|
+
*/
|
|
132
|
+
getRuleHostContextRows(sessionId: string, limit?: number): RuleHostContextResult;
|
|
124
133
|
/**
|
|
125
134
|
* List pain events for a session.
|
|
126
135
|
*
|
package/dist/core/trajectory.js
CHANGED
|
@@ -903,6 +903,36 @@ export class TrajectoryDatabase {
|
|
|
903
903
|
};
|
|
904
904
|
});
|
|
905
905
|
}
|
|
906
|
+
/**
|
|
907
|
+
* PRI-482 Phase 3: Query tool_calls for RuleContext v2 history assembly.
|
|
908
|
+
*
|
|
909
|
+
* Reads limit+1 rows (DESC by id) to compute truncated, then reverses to FIFO.
|
|
910
|
+
* Returns raw rows — the assembler (rule-context-assembler.ts) validates them.
|
|
911
|
+
*
|
|
912
|
+
* Spec: §5.1, §5.2. ERR-026: reuses production schema (no hand-written DDL).
|
|
913
|
+
*/
|
|
914
|
+
getRuleHostContextRows(sessionId, limit = 20) {
|
|
915
|
+
const cappedLimit = Math.max(1, Math.floor(limit));
|
|
916
|
+
const rows = this.db.prepare(`
|
|
917
|
+
SELECT id, tool_name, outcome, params_json
|
|
918
|
+
FROM tool_calls
|
|
919
|
+
WHERE session_id = ?
|
|
920
|
+
ORDER BY id DESC
|
|
921
|
+
LIMIT ?
|
|
922
|
+
`).all(sessionId, cappedLimit + 1);
|
|
923
|
+
const truncated = rows.length > cappedLimit;
|
|
924
|
+
const output = truncated ? rows.slice(0, cappedLimit) : rows;
|
|
925
|
+
output.reverse(); // DESC → FIFO (oldest first)
|
|
926
|
+
return {
|
|
927
|
+
rows: output.map((row) => ({
|
|
928
|
+
id: Number(row.id),
|
|
929
|
+
toolName: String(row.tool_name),
|
|
930
|
+
outcome: String(row.outcome),
|
|
931
|
+
paramsJson: String(row.params_json),
|
|
932
|
+
})),
|
|
933
|
+
truncated,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
906
936
|
/**
|
|
907
937
|
* List pain events for a session.
|
|
908
938
|
*
|
package/dist/hooks/gate.js
CHANGED
|
@@ -17,7 +17,7 @@ import { AGENT_TOOLS, BASH_TOOLS_SET, WRITE_TOOLS } from '../constants/tools.js'
|
|
|
17
17
|
import { getSession, hasRecentThinking } from '../core/session-tracker.js';
|
|
18
18
|
import { getEvolutionEngine } from '../core/evolution-engine.js';
|
|
19
19
|
import { EventLogService } from '../core/event-log.js';
|
|
20
|
-
import { estimateLineChanges } from '
|
|
20
|
+
import { estimateLineChanges } from '@principles/core/runtime-v2';
|
|
21
21
|
export function handleBeforeToolCall(event, ctx) {
|
|
22
22
|
const logger = ctx.logger || console;
|
|
23
23
|
// 1. Identify tool type
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "1.159.0",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onCapabilities": [
|
|
8
8
|
"hook"
|
package/package.json
CHANGED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
2
|
-
export interface FileModification {
|
|
3
|
-
toolName: string;
|
|
4
|
-
params: Record<string, unknown>;
|
|
5
|
-
}
|
|
6
|
-
export declare function estimateLineChanges(modification: FileModification): number;
|
|
7
|
-
export declare function assessRiskLevel(filePath: string, modification: FileModification, riskPaths: string[]): RiskLevel;
|
|
8
|
-
/**
|
|
9
|
-
* Get the total line count of a target file.
|
|
10
|
-
* @param absoluteFilePath - Absolute path to the file
|
|
11
|
-
* @returns File line count, or null if file doesn't exist or can't be read
|
|
12
|
-
*/
|
|
13
|
-
export declare function getTargetFileLineCount(absoluteFilePath: string): number | null;
|
|
14
|
-
/**
|
|
15
|
-
* Calculate the effective line limit based on percentage of target file.
|
|
16
|
-
* @param targetLineCount - Total lines in target file
|
|
17
|
-
* @param percentage - Allowed percentage (0-100)
|
|
18
|
-
* @param minLines - Absolute minimum threshold
|
|
19
|
-
* @param maxLines - Optional upper bound to prevent misconfiguration
|
|
20
|
-
* @returns Maximum allowed lines (at least minLines, at most maxLines if provided)
|
|
21
|
-
*/
|
|
22
|
-
export declare function calculatePercentageThreshold(targetLineCount: number, percentage: number, minLines: number, maxLines?: number): number;
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
/* global NodeJS */
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import { isRisky } from '../utils/io.js';
|
|
4
|
-
export function estimateLineChanges(modification) {
|
|
5
|
-
const { toolName, params } = modification;
|
|
6
|
-
if (toolName === 'write_file' || toolName === 'write') {
|
|
7
|
-
const content = params.content || '';
|
|
8
|
-
return content.split('\n').length;
|
|
9
|
-
}
|
|
10
|
-
if (toolName === 'replace' || toolName === 'edit') {
|
|
11
|
-
const newContent = params.new_string || params.newText || '';
|
|
12
|
-
return newContent.split('\n').length;
|
|
13
|
-
}
|
|
14
|
-
if (toolName === 'apply_patch' || toolName === 'patch') {
|
|
15
|
-
const patch = params.patch || '';
|
|
16
|
-
// Rough estimate for patch files
|
|
17
|
-
return patch.split('\n').filter((l) => l.startsWith('+') || l.startsWith('-')).length;
|
|
18
|
-
}
|
|
19
|
-
if (toolName === 'delete_file') {
|
|
20
|
-
// Deleting a file is considered a significant change, but we don't know the size.
|
|
21
|
-
// We'll treat it as a medium-to-large size change.
|
|
22
|
-
return 50;
|
|
23
|
-
}
|
|
24
|
-
return 0;
|
|
25
|
-
}
|
|
26
|
-
export function assessRiskLevel(filePath, modification, riskPaths) {
|
|
27
|
-
const isRiskPath = isRisky(filePath, riskPaths);
|
|
28
|
-
const estimatedLines = estimateLineChanges(modification);
|
|
29
|
-
if (isRiskPath) {
|
|
30
|
-
if (estimatedLines > 100)
|
|
31
|
-
return 'CRITICAL';
|
|
32
|
-
return 'HIGH';
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
if (estimatedLines > 100)
|
|
36
|
-
return 'HIGH';
|
|
37
|
-
if (estimatedLines > 10)
|
|
38
|
-
return 'MEDIUM';
|
|
39
|
-
return 'LOW';
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Get the total line count of a target file.
|
|
44
|
-
* @param absoluteFilePath - Absolute path to the file
|
|
45
|
-
* @returns File line count, or null if file doesn't exist or can't be read
|
|
46
|
-
*/
|
|
47
|
-
export function getTargetFileLineCount(absoluteFilePath) {
|
|
48
|
-
try {
|
|
49
|
-
if (!fs.existsSync(absoluteFilePath)) {
|
|
50
|
-
return null; // File genuinely doesn't exist
|
|
51
|
-
}
|
|
52
|
-
const stats = fs.statSync(absoluteFilePath);
|
|
53
|
-
if (!stats.isFile()) {
|
|
54
|
-
return null; // Not a regular file (directory, device, etc.)
|
|
55
|
-
}
|
|
56
|
-
const content = fs.readFileSync(absoluteFilePath, 'utf-8');
|
|
57
|
-
return content.split('\n').length;
|
|
58
|
-
}
|
|
59
|
-
catch (e) {
|
|
60
|
-
// Log error before falling back to null - this is intentional for security gates
|
|
61
|
-
const error = e instanceof Error ? e : new Error(String(e));
|
|
62
|
-
const errorCode = e.code;
|
|
63
|
-
console.error(`[PD:RISK_CALC] Failed to read file for line count: ${absoluteFilePath}`, {
|
|
64
|
-
code: errorCode,
|
|
65
|
-
message: error.message,
|
|
66
|
-
});
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Calculate the effective line limit based on percentage of target file.
|
|
72
|
-
* @param targetLineCount - Total lines in target file
|
|
73
|
-
* @param percentage - Allowed percentage (0-100)
|
|
74
|
-
* @param minLines - Absolute minimum threshold
|
|
75
|
-
* @param maxLines - Optional upper bound to prevent misconfiguration
|
|
76
|
-
* @returns Maximum allowed lines (at least minLines, at most maxLines if provided)
|
|
77
|
-
*/
|
|
78
|
-
export function calculatePercentageThreshold(targetLineCount, percentage, minLines, maxLines) {
|
|
79
|
-
// Clamp percentage to valid range [0, 100]
|
|
80
|
-
const clampedPercentage = Math.max(0, Math.min(100, percentage));
|
|
81
|
-
const calculated = Math.round(targetLineCount * (clampedPercentage / 100));
|
|
82
|
-
let effectiveLimit = Math.max(calculated, minLines);
|
|
83
|
-
// Apply optional upper bound
|
|
84
|
-
if (maxLines !== undefined && maxLines > 0) {
|
|
85
|
-
effectiveLimit = Math.min(effectiveLimit, maxLines);
|
|
86
|
-
}
|
|
87
|
-
return effectiveLimit;
|
|
88
|
-
}
|