@perceo/observer-engine 0.1.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.
@@ -0,0 +1,149 @@
1
+ interface ObserverEngineConfig {
2
+ observer: {
3
+ watch: {
4
+ paths: string[];
5
+ ignore?: string[];
6
+ debounceMs?: number;
7
+ autoTest?: boolean;
8
+ };
9
+ ci?: {
10
+ strategy?: "affected-flows" | string;
11
+ parallelism?: number;
12
+ };
13
+ analysis?: {
14
+ useLLM?: boolean;
15
+ llmThreshold?: number;
16
+ };
17
+ /**
18
+ * Optional base URL and API key for managed Observer Engine APIs.
19
+ * When present, bootstrap and analysis operations will be delegated
20
+ * to the remote service.
21
+ */
22
+ apiBaseUrl?: string;
23
+ apiKey?: string;
24
+ };
25
+ flowGraph?: {
26
+ endpoint: string;
27
+ database?: string;
28
+ };
29
+ eventBus?: {
30
+ type: "in-memory" | "redis";
31
+ redisUrl?: string;
32
+ };
33
+ }
34
+ interface ChangeAnalysisFile {
35
+ path: string;
36
+ status: "added" | "modified" | "deleted" | "renamed";
37
+ }
38
+ interface ChangeAnalysis {
39
+ baseSha: string;
40
+ headSha: string;
41
+ files: ChangeAnalysisFile[];
42
+ }
43
+ interface ImpactedFlow {
44
+ name: string;
45
+ confidence: number;
46
+ riskScore: number;
47
+ priority?: "critical" | "high" | "medium" | "low";
48
+ }
49
+ /**
50
+ * Shape aligned with the ANALYSIS_COMPLETE event described in docs/cli_architecture.md.
51
+ */
52
+ interface ImpactReport {
53
+ changeId: string;
54
+ baseSha: string;
55
+ headSha: string;
56
+ flows: ImpactedFlow[];
57
+ changes: ChangeAnalysisFile[];
58
+ createdAt: number;
59
+ }
60
+ interface BootstrapResult {
61
+ projectName: string;
62
+ framework: string;
63
+ flowsInitialized: number;
64
+ personasInitialized: number;
65
+ warnings?: string[];
66
+ }
67
+ interface BootstrapOptions {
68
+ projectDir: string;
69
+ projectName: string;
70
+ framework: string;
71
+ }
72
+
73
+ interface PerceoEvent<T = any> {
74
+ id: string;
75
+ type: string;
76
+ timestamp: number;
77
+ source: "observer" | "coordinator" | "analytics" | "analyzer" | "graph";
78
+ data: T;
79
+ metadata?: {
80
+ userId?: string;
81
+ projectId?: string;
82
+ environment?: string;
83
+ };
84
+ }
85
+ interface EventBusLike {
86
+ publish<T>(event: PerceoEvent<T>): Promise<void> | void;
87
+ }
88
+ interface FlowGraphClientLike {
89
+ upsertFlows?(flows: unknown[]): Promise<void>;
90
+ }
91
+ interface ObserverEngineDeps {
92
+ eventBus?: EventBusLike;
93
+ flowGraph?: FlowGraphClientLike;
94
+ }
95
+ declare class ObserverEngine {
96
+ private readonly config;
97
+ private readonly deps;
98
+ private readonly apiClient;
99
+ constructor(config: ObserverEngineConfig, deps?: ObserverEngineDeps);
100
+ /**
101
+ * Bootstrap flows/personas for the current project.
102
+ *
103
+ * Delegates to the managed Observer API when configured, otherwise returns
104
+ * a fast local no-op result so init remains non-blocking.
105
+ */
106
+ bootstrapProject(options: BootstrapOptions): Promise<BootstrapResult>;
107
+ /**
108
+ * Analyze changes between two Git refs and return an ImpactReport.
109
+ *
110
+ * Uses local Git diff to build a ChangeAnalysis, then delegates to the
111
+ * managed Observer API when configured. If no API is configured, returns
112
+ * a minimal, local-only report that still captures the changed files.
113
+ */
114
+ analyzeChanges(params: {
115
+ baseSha: string;
116
+ headSha: string;
117
+ projectRoot: string;
118
+ }): Promise<ImpactReport>;
119
+ /**
120
+ * Core entry point for watch-mode integration.
121
+ *
122
+ * The CLI is expected to provide a stream of file changes; this method
123
+ * will wire those into the Observer pipeline and return a disposer to
124
+ * stop processing. The detailed implementation will be filled in when
125
+ * watch support is built out.
126
+ */
127
+ startWatchCore(_options: {
128
+ onChange: (change: {
129
+ path: string;
130
+ type: "add" | "change" | "unlink";
131
+ }) => void | Promise<void>;
132
+ }): Promise<() => Promise<void>>;
133
+ }
134
+
135
+ interface ObserverApiClientOptions {
136
+ baseUrl: string;
137
+ apiKey?: string;
138
+ }
139
+ declare class ObserverApiClient {
140
+ private readonly baseUrl;
141
+ private readonly apiKey?;
142
+ constructor(options: ObserverApiClientOptions);
143
+ static fromConfig(config: ObserverEngineConfig): ObserverApiClient | null;
144
+ bootstrapProject(options: BootstrapOptions): Promise<BootstrapResult>;
145
+ analyzeChanges(change: ChangeAnalysis): Promise<ImpactReport>;
146
+ private post;
147
+ }
148
+
149
+ export { type BootstrapOptions, type BootstrapResult, type ChangeAnalysis, type ChangeAnalysisFile, type EventBusLike, type FlowGraphClientLike, type ImpactReport, type ImpactedFlow, ObserverApiClient, type ObserverApiClientOptions, ObserverEngine, type ObserverEngineConfig, type ObserverEngineDeps, type PerceoEvent };
package/dist/index.js ADDED
@@ -0,0 +1,169 @@
1
+ // src/client.ts
2
+ var ObserverApiClient = class _ObserverApiClient {
3
+ baseUrl;
4
+ apiKey;
5
+ constructor(options) {
6
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
7
+ this.apiKey = options.apiKey;
8
+ }
9
+ static fromConfig(config) {
10
+ const baseUrl = config.observer.apiBaseUrl;
11
+ if (!baseUrl) return null;
12
+ return new _ObserverApiClient({
13
+ baseUrl,
14
+ apiKey: config.observer.apiKey
15
+ });
16
+ }
17
+ async bootstrapProject(options) {
18
+ const res = await this.post("/observer/bootstrap", options);
19
+ return res;
20
+ }
21
+ async analyzeChanges(change) {
22
+ const res = await this.post("/observer/analyze", change);
23
+ return res;
24
+ }
25
+ async post(path, body) {
26
+ const url = `${this.baseUrl}${path}`;
27
+ const headers = {
28
+ "Content-Type": "application/json"
29
+ };
30
+ if (this.apiKey) {
31
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
32
+ }
33
+ const response = await fetch(url, {
34
+ method: "POST",
35
+ headers,
36
+ body: JSON.stringify(body)
37
+ });
38
+ if (!response.ok) {
39
+ const text = await response.text().catch(() => "");
40
+ throw new Error(`Observer API request failed (${response.status}): ${text || response.statusText}`);
41
+ }
42
+ return await response.json();
43
+ }
44
+ };
45
+
46
+ // src/git.ts
47
+ import { exec as _exec } from "child_process";
48
+ import { promisify } from "util";
49
+ var exec = promisify(_exec);
50
+ async function computeChangeAnalysis(projectRoot, baseSha, headSha) {
51
+ const { stdout } = await exec(`git diff --name-status ${baseSha}...${headSha}`, {
52
+ cwd: projectRoot
53
+ });
54
+ const files = stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => {
55
+ const [status, filePath] = line.split(/\s+/, 2);
56
+ return {
57
+ path: normalizePath(filePath),
58
+ status: mapStatus(status)
59
+ };
60
+ });
61
+ return {
62
+ baseSha,
63
+ headSha,
64
+ files
65
+ };
66
+ }
67
+ function mapStatus(raw) {
68
+ switch (raw) {
69
+ case "A":
70
+ return "added";
71
+ case "D":
72
+ return "deleted";
73
+ case "R":
74
+ case "R100":
75
+ case "R099":
76
+ return "renamed";
77
+ case "M":
78
+ default:
79
+ return "modified";
80
+ }
81
+ }
82
+ function normalizePath(p) {
83
+ if (!p) return "";
84
+ return p.split("\\").join("/");
85
+ }
86
+
87
+ // src/engine.ts
88
+ var ObserverEngine = class {
89
+ config;
90
+ deps;
91
+ apiClient;
92
+ constructor(config, deps = {}) {
93
+ this.config = config;
94
+ this.deps = deps;
95
+ this.apiClient = ObserverApiClient.fromConfig(config);
96
+ }
97
+ /**
98
+ * Bootstrap flows/personas for the current project.
99
+ *
100
+ * Delegates to the managed Observer API when configured, otherwise returns
101
+ * a fast local no-op result so init remains non-blocking.
102
+ */
103
+ async bootstrapProject(options) {
104
+ if (!this.apiClient) {
105
+ return {
106
+ projectName: options.projectName,
107
+ framework: options.framework,
108
+ flowsInitialized: 0,
109
+ personasInitialized: 0,
110
+ warnings: ["Observer managed API is not configured; skipping remote bootstrap."]
111
+ };
112
+ }
113
+ const result = await this.apiClient.bootstrapProject(options);
114
+ if (this.deps.flowGraph && Array.isArray(result.flows)) {
115
+ await this.deps.flowGraph.upsertFlows?.(result.flows);
116
+ }
117
+ return result;
118
+ }
119
+ /**
120
+ * Analyze changes between two Git refs and return an ImpactReport.
121
+ *
122
+ * Uses local Git diff to build a ChangeAnalysis, then delegates to the
123
+ * managed Observer API when configured. If no API is configured, returns
124
+ * a minimal, local-only report that still captures the changed files.
125
+ */
126
+ async analyzeChanges(params) {
127
+ const change = await computeChangeAnalysis(params.projectRoot, params.baseSha, params.headSha);
128
+ let report;
129
+ if (this.apiClient) {
130
+ report = await this.apiClient.analyzeChanges(change);
131
+ } else {
132
+ report = {
133
+ changeId: `${params.baseSha}...${params.headSha}`,
134
+ baseSha: params.baseSha,
135
+ headSha: params.headSha,
136
+ flows: [],
137
+ changes: change.files,
138
+ createdAt: Date.now()
139
+ };
140
+ }
141
+ if (this.deps.eventBus) {
142
+ const event = {
143
+ id: report.changeId,
144
+ type: "observer.analysis.complete",
145
+ timestamp: report.createdAt,
146
+ source: "observer",
147
+ data: report
148
+ };
149
+ await this.deps.eventBus.publish(event);
150
+ }
151
+ return report;
152
+ }
153
+ /**
154
+ * Core entry point for watch-mode integration.
155
+ *
156
+ * The CLI is expected to provide a stream of file changes; this method
157
+ * will wire those into the Observer pipeline and return a disposer to
158
+ * stop processing. The detailed implementation will be filled in when
159
+ * watch support is built out.
160
+ */
161
+ async startWatchCore(_options) {
162
+ return async () => {
163
+ };
164
+ }
165
+ };
166
+ export {
167
+ ObserverApiClient,
168
+ ObserverEngine
169
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@perceo/observer-engine",
3
+ "version": "0.1.0",
4
+ "description": "Observer Engine core used by the Perceo CLI for flow/persona bootstrap and change impact analysis.",
5
+ "keywords": [
6
+ "perceo",
7
+ "observer",
8
+ "analysis",
9
+ "flows",
10
+ "ci"
11
+ ],
12
+ "license": "MIT",
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "devDependencies": {
20
+ "@types/node": "^22.15.3",
21
+ "tsup": "^8.0.1",
22
+ "typescript": "5.9.2",
23
+ "@repo/typescript-config": "0.1.0"
24
+ },
25
+ "peerDependencies": {
26
+ "@perceo/perceo": "0.x"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup src/index.ts --format esm --dts --clean",
30
+ "dev": "tsup src/index.ts --format esm --watch",
31
+ "check-types": "tsc --noEmit"
32
+ }
33
+ }