deepdebug-local-agent 1.0.22 → 1.0.23
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/README.md +81 -0
- package/docs/legacy-connectors.md +268 -0
- package/package.json +4 -1
- package/scripts/sync-fixtures.mjs +246 -0
- package/src/adapters/as400/as400-parser.js +12 -0
- package/src/adapters/as400/index.js +134 -0
- package/src/adapters/cobol/cobol-parser.js +37 -0
- package/src/adapters/cobol/index.js +151 -0
- package/src/adapters/ibmmq/ibmmq-parser.js +23 -0
- package/src/adapters/ibmmq/index.js +137 -0
- package/src/adapters/index.js +79 -0
- package/src/adapters/oracle-forms/index.js +145 -0
- package/src/adapters/oracle-forms/oracle-forms-parser.js +11 -0
- package/src/adapters/sap/index.js +146 -0
- package/src/adapters/sap/sap-parser.js +41 -0
- package/src/server.js +46 -2
- package/test/adapters/as400/as400.test.js +121 -0
- package/test/adapters/cobol/cobol.test.js +217 -0
- package/test/adapters/ibmmq/ibmmq.test.js +122 -0
- package/test/adapters/oracle-forms/oracle-forms.test.js +180 -0
- package/test/adapters/registry.test.js +101 -0
- package/test/adapters/sap/sap.test.js +142 -0
- package/src/local-agent-source.tar.gz +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# DeepDebug Local Agent
|
|
2
|
+
|
|
3
|
+
AI-powered on-prem ingestion arm of the VisionHammer / DeepDebug pipeline. The
|
|
4
|
+
local agent watches application logs, surfaces incidents, and forwards them to
|
|
5
|
+
the gateway for downstream analysis.
|
|
6
|
+
|
|
7
|
+
- **Runtime:** Node.js 18+ (pure ESM, `"type": "module"`)
|
|
8
|
+
- **Test runner:** `node:test` (Node built-in)
|
|
9
|
+
- **Distribution:** `npm install` for development; `pkg`-bundled binaries for
|
|
10
|
+
production install (`npm run build:all`)
|
|
11
|
+
- **Package:** `deepdebug-local-agent@1.0.21`
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install
|
|
17
|
+
npm test # runs node --test (52 tests across adapters + registry)
|
|
18
|
+
npm start # boots the agent (default port 5055; MCP bridge on 5056)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Configuration lives in `~/.deepdebug/config.json` and via env vars (`PORT`,
|
|
22
|
+
`MCP_PORT`, `GATEWAY_URL`, `DEEPDEBUG_API_KEY`, `TENANT_ID`,
|
|
23
|
+
`DISABLE_WS_TUNNEL`, `LEGACY_STACKS`).
|
|
24
|
+
|
|
25
|
+
Companion docs:
|
|
26
|
+
|
|
27
|
+
- [QUICKSTART.md](./docs/QUICKSTART.md) — end-user install + first-run
|
|
28
|
+
- [ENTERPRISE_DEPLOYMENT_GUIDE.md](./docs/ENTERPRISE_DEPLOYMENT_GUIDE.md)
|
|
29
|
+
- [SECURITY_WHITEPAPER.md](./docs/SECURITY_WHITEPAPER.md)
|
|
30
|
+
- [docs/legacy-connectors.md](./docs/legacy-connectors.md) — **Phase 2** legacy
|
|
31
|
+
stack ingestion reference
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- Real-time application log ingestion via `processManager.on('log', ...)`
|
|
36
|
+
- AI-driven patch generation (`AIVibeCodingEngine`) wired to the gateway
|
|
37
|
+
- Workspace-scoped operations via the workspace manager
|
|
38
|
+
- Backup / restore with disk-indexed history
|
|
39
|
+
- WebSocket reverse tunnel to the gateway (`startWebSocketTunnel`)
|
|
40
|
+
- MCP HTTP bridge (`startMCPHttpServer` on port 5056)
|
|
41
|
+
- **Legacy connectors** — five EventEmitter adapters for legacy on-prem stacks
|
|
42
|
+
(see below)
|
|
43
|
+
|
|
44
|
+
## Legacy connectors
|
|
45
|
+
|
|
46
|
+
DeepDebug Local Agent ships five EventEmitter adapters for legacy on-prem
|
|
47
|
+
stacks, behind a single `LEGACY_STACKS` env var:
|
|
48
|
+
|
|
49
|
+
- **SAP** (`src/adapters/sap/`) — watches SAP NetWeaver `dev_w*` work-process
|
|
50
|
+
traces + `SLOG*` system logs
|
|
51
|
+
- **IBM MQ** (`src/adapters/ibmmq/`) — watches `AMQERR0[1-3].LOG` files
|
|
52
|
+
- **AS/400** (`src/adapters/as400/`) — polls DSPLOG via SSH with auto-reconnect
|
|
53
|
+
- **Oracle Forms** (`src/adapters/oracle-forms/`) — watches `*.trc` trace files
|
|
54
|
+
(Latin-1 tolerant)
|
|
55
|
+
- **COBOL/Mainframe** (`src/adapters/cobol/`) — watches JES2 spool exports
|
|
56
|
+
(CRLF + 80-col tolerant)
|
|
57
|
+
|
|
58
|
+
Each adapter emits `error` events with a frozen 6-key shape:
|
|
59
|
+
`{adapter, errorCode, file, message, source, timestamp}`.
|
|
60
|
+
|
|
61
|
+
To enable: set `LEGACY_STACKS=sap,cobol` (comma-separated subset of
|
|
62
|
+
`sap,ibmmq,as400,oracle-forms,cobol`) in the agent's environment. When unset,
|
|
63
|
+
the adapters are not loaded and the local-agent boot path is byte-for-byte
|
|
64
|
+
unchanged.
|
|
65
|
+
|
|
66
|
+
Full configuration + troubleshooting reference:
|
|
67
|
+
[docs/legacy-connectors.md](./docs/legacy-connectors.md).
|
|
68
|
+
|
|
69
|
+
## Development
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm test # full suite (52 tests)
|
|
73
|
+
npm run sync-fixtures # refresh test/fixtures/<stack>/ from example repos
|
|
74
|
+
# (requires ~/.config/visionhammer/github-pat)
|
|
75
|
+
npm run lint # eslint src/
|
|
76
|
+
npm run dev # NODE_ENV=development node src/server.js
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
Proprietary — InspTech AI.
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# Legacy connectors — configuration & troubleshooting
|
|
2
|
+
|
|
3
|
+
Phase 2 of VisionHammer ships five Node.js EventEmitter adapters that ingest
|
|
4
|
+
logs from legacy on-prem systems into the DeepDebug pipeline. This document is
|
|
5
|
+
the operator's reference for enabling and tuning them.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
1. Read [Setup](#setup) to configure your GitHub PAT (only needed if you plan to
|
|
10
|
+
run `npm run sync-fixtures` to refresh test fixtures).
|
|
11
|
+
2. Set `LEGACY_STACKS=<stacks>` in the agent's environment to choose which
|
|
12
|
+
adapters boot. Example: `LEGACY_STACKS=sap,cobol`.
|
|
13
|
+
3. Provide per-adapter config via env or `agent.yaml` (see
|
|
14
|
+
[Configuration](#configuration) below).
|
|
15
|
+
4. Restart the local agent. Adapters start automatically and stop within 5 s on
|
|
16
|
+
`SIGTERM`.
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
The `npm run sync-fixtures` script clones the
|
|
21
|
+
`williambella/visionhammer-example-*` GitHub repos to refresh
|
|
22
|
+
`test/fixtures/<stack>/` from upstream. It needs a GitHub PAT.
|
|
23
|
+
|
|
24
|
+
1. On github.com → Developer settings → Personal access tokens → Tokens
|
|
25
|
+
(classic) → Generate new token. Scope: `repo` (read-only is sufficient).
|
|
26
|
+
2. Save the PAT to `~/.config/visionhammer/github-pat` with permissions `0600`:
|
|
27
|
+
```bash
|
|
28
|
+
mkdir -p ~/.config/visionhammer
|
|
29
|
+
echo "YOUR_PAT_HERE" > ~/.config/visionhammer/github-pat
|
|
30
|
+
chmod 600 ~/.config/visionhammer/github-pat
|
|
31
|
+
```
|
|
32
|
+
3. **Never paste** the PAT into a shell env var. The script reads the file
|
|
33
|
+
directly at invocation time and validates the token against `GET /user`
|
|
34
|
+
before any clone. (T-2-04 + T-2-05 mitigation.)
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
Adapters are configured via the `LEGACY_STACKS` env var (which stacks to boot)
|
|
39
|
+
plus per-adapter env vars or `agent.yaml` entries.
|
|
40
|
+
|
|
41
|
+
### Supported stacks
|
|
42
|
+
|
|
43
|
+
| Stack | Adapter dir | Default config | Required mounts/access |
|
|
44
|
+
| -------------- | -------------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
45
|
+
| `sap` | `src/adapters/sap/` | `{sapDir: '/usr/sap/PRD', instanceNumber: '00', interval: 5000}` | NFS/SMB mount of `/usr/sap/<SID>/` readable by the agent user |
|
|
46
|
+
| `ibmmq` | `src/adapters/ibmmq/` | `{errorsDir: '/var/mqm/qmgrs/QM_PRD01/errors', interval: 5000}` | Read access to qmgr `errors/` directory |
|
|
47
|
+
| `as400` | `src/adapters/as400/` | `{host: 'as400-prd-01', port: 22, pollInterval: 30000}` | SSH credentials (password OR private-key); user with `DSPLOG` authority |
|
|
48
|
+
| `oracle-forms` | `src/adapters/oracle-forms/` | `{traceDir: '/u01/oracle/forms/trace', interval: 5000}` | Read access to `FORMS_TRACE_DIR` |
|
|
49
|
+
| `cobol` | `src/adapters/cobol/` | `{spoolDir: '/var/spool/jes2', interval: 5000}` | Read access to JES2 spool export dir |
|
|
50
|
+
|
|
51
|
+
Glob anchors (per-adapter, hard-coded):
|
|
52
|
+
|
|
53
|
+
- **SAP** watches `<sapDir>/D<instanceNumber>/work/dev_w*` AND
|
|
54
|
+
`<sapDir>/D<instanceNumber>/log/SLOG*` (T-2-03 mitigation — narrow regex
|
|
55
|
+
surface).
|
|
56
|
+
- **IBM MQ** watches `<errorsDir>/AMQERR*.LOG` then enforces filename anchor
|
|
57
|
+
`^AMQERR0[1-3]\.LOG$` in-handler (chokidar bracket-class workaround on macOS
|
|
58
|
+
/ Node 24 / chokidar 3.6.0).
|
|
59
|
+
- **Oracle Forms** watches `<traceDir>/*.trc` only (`*.log` is **not** read).
|
|
60
|
+
- **COBOL** watches `<spoolDir>/*` with `depth: 0` (broad spool glob — JES2
|
|
61
|
+
exports use arbitrary file names; non-files are filtered with `isFile()`).
|
|
62
|
+
- **AS/400** does NOT watch files; it polls the literal command
|
|
63
|
+
`system "DSPLOG OUTPUT(*PRINT) LOGTYPE(*MSG) MSGTYPE(*ESCAPE *NOTIFY)"` over
|
|
64
|
+
SSH every `pollInterval` ms.
|
|
65
|
+
|
|
66
|
+
### Per-adapter config sub-objects
|
|
67
|
+
|
|
68
|
+
The registry's
|
|
69
|
+
`loadAdapters({legacyStacks, sap, ibmmq, as400, 'oracle-forms', cobol})`
|
|
70
|
+
accepts one sub-object per stack. Each is passed straight to the adapter
|
|
71
|
+
constructor. Override defaults by setting the corresponding key. Example:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
import { loadAdapters, stopAll } from "./src/adapters/index.js";
|
|
75
|
+
|
|
76
|
+
const adapters = loadAdapters({
|
|
77
|
+
legacyStacks: ['sap', 'cobol'],
|
|
78
|
+
sap: { sapDir: '/sapmnt/PRD', instanceNumber: '01', interval: 10000 },
|
|
79
|
+
cobol: { spoolDir: '/mnt/zos/spool', interval: 30000 },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
for (const a of adapters) await a.start();
|
|
83
|
+
// ... later, on shutdown
|
|
84
|
+
await stopAll(adapters, 5000);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`loadAdapters` returns adapters **unstarted** (AD-1 contract). The caller owns
|
|
88
|
+
the lifecycle. The `LEGACY_STACKS` env-var bootstrap path in `src/server.js`
|
|
89
|
+
already wires `loadAdapters` + `start()` for you when the env var is set.
|
|
90
|
+
|
|
91
|
+
## Event shape
|
|
92
|
+
|
|
93
|
+
Every adapter emits `error` events with EXACTLY this shape (frozen, 6 keys, no
|
|
94
|
+
extras — alphabetical key order matters for the failsafe contract test):
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
{
|
|
98
|
+
adapter: 'sap' | 'ibmmq' | 'as400' | 'oracle-forms' | 'cobol',
|
|
99
|
+
errorCode: 'ORA-01403' | 'AMQ7472W' | 'CPF1234' | 'S0C7' | null,
|
|
100
|
+
file: '/usr/sap/PRD/D00/work/dev_w0',
|
|
101
|
+
message: '<verbatim matched line>',
|
|
102
|
+
source: '/usr/sap/PRD/D00/work',
|
|
103
|
+
timestamp: '2024-01-15T09:12:44.000Z'
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Per AD-5, events are ordered by byte position **within a single file**.
|
|
108
|
+
Cross-file and cross-adapter ordering are explicit non-guarantees — the
|
|
109
|
+
downstream gateway pipeline correlates by `incidentId`, not by event sequence.
|
|
110
|
+
|
|
111
|
+
`timestamp` is the moment the adapter detected the line (not the timestamp
|
|
112
|
+
inside the log line — log-line timestamps have inconsistent precision across
|
|
113
|
+
formats).
|
|
114
|
+
|
|
115
|
+
## Sync fixtures script
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Sync all 5 stacks
|
|
119
|
+
npm run sync-fixtures
|
|
120
|
+
|
|
121
|
+
# Sync only a subset
|
|
122
|
+
node scripts/sync-fixtures.mjs --stacks=oracle-forms,cobol
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
What it does, in order, for each requested stack:
|
|
126
|
+
|
|
127
|
+
1. Reads the PAT once from `~/.config/visionhammer/github-pat`.
|
|
128
|
+
2. Validates the PAT via `GET https://api.github.com/user` (T-2-05).
|
|
129
|
+
3. Shallow-clones `williambella/visionhammer-example-<stack>` to
|
|
130
|
+
`/tmp/sync-<stack>` via `git clone --depth 1 --branch main` with the PAT
|
|
131
|
+
embedded in the URL **only as a `child_process.spawn` argv element**, never
|
|
132
|
+
echoed to stdout/stderr/argv-string/env (T-2-04).
|
|
133
|
+
4. Copies `samples/*.log` (case-insensitive) into `test/fixtures/<dir>/`
|
|
134
|
+
(mapping: `sap-abap → sap`, `ibm-mq → ibmmq`, others identity).
|
|
135
|
+
5. Renames IBM MQ fixtures so they pass the adapter's `^AMQERR0[1-3]\.LOG$`
|
|
136
|
+
filename anchor:
|
|
137
|
+
|
|
138
|
+
| Source filename | Target filename |
|
|
139
|
+
| ---------------------------------------- | --------------- |
|
|
140
|
+
| `ibm-mq-sample-mqrc-2033.log` | `AMQERR01.LOG` |
|
|
141
|
+
| `ibm-mq-sample-mqrc-2058.log` | `AMQERR02.LOG` |
|
|
142
|
+
| `ibm-mq-sample-resource-leak.log` | `AMQERR03.LOG` |
|
|
143
|
+
|
|
144
|
+
6. Removes the `/tmp/sync-<stack>` clone scratch dir.
|
|
145
|
+
|
|
146
|
+
Idempotent — re-runs cleanly overwrite prior fixtures and rewipe scratch dirs.
|
|
147
|
+
On git failure the embedded PAT is replaced with `***REDACTED***` before
|
|
148
|
+
stderr is written.
|
|
149
|
+
|
|
150
|
+
## Example fixture repos
|
|
151
|
+
|
|
152
|
+
| Stack | Repo |
|
|
153
|
+
| ----------------- | ------------------------------------------------------------------------------------------ |
|
|
154
|
+
| Oracle Forms 12c | [williambella/visionhammer-example-oracle-forms](https://github.com/williambella/visionhammer-example-oracle-forms) |
|
|
155
|
+
| Oracle PL/SQL | [williambella/visionhammer-example-plsql](https://github.com/williambella/visionhammer-example-plsql) |
|
|
156
|
+
| SAP ABAP | [williambella/visionhammer-example-sap-abap](https://github.com/williambella/visionhammer-example-sap-abap) |
|
|
157
|
+
| COBOL / z/OS | [williambella/visionhammer-example-cobol](https://github.com/williambella/visionhammer-example-cobol) |
|
|
158
|
+
| IBM MQ Java | [williambella/visionhammer-example-ibm-mq](https://github.com/williambella/visionhammer-example-ibm-mq) |
|
|
159
|
+
|
|
160
|
+
Each repo carries: an intentional-bug `README.md`, source code reproducing each
|
|
161
|
+
bug, `samples/<stack>-sample-<n>.log` synthetic logs, and `samples/README.md`
|
|
162
|
+
with `## Source citation`, `## Product version`, and
|
|
163
|
+
`## Zero PII confirmation` sections per AD-6.
|
|
164
|
+
|
|
165
|
+
## Troubleshooting
|
|
166
|
+
|
|
167
|
+
### CPU usage too high (T-2-01)
|
|
168
|
+
|
|
169
|
+
Default poll intervals are conservative (5 s for chokidar-based adapters,
|
|
170
|
+
30 s for AS/400 SSH). If you see elevated CPU on the agent host or the legacy
|
|
171
|
+
system:
|
|
172
|
+
|
|
173
|
+
- Increase `interval` for chokidar adapters (sap, ibmmq, oracle-forms, cobol).
|
|
174
|
+
Try `10000` (10 s) first.
|
|
175
|
+
- Increase `pollInterval` for AS/400. Try `60000` (60 s).
|
|
176
|
+
- Do **not** set `interval < 1000`. NFS/SMB watch polling at sub-second cadence
|
|
177
|
+
is pathological for production systems.
|
|
178
|
+
|
|
179
|
+
### AS/400 SSH session keeps dropping (T-2-02)
|
|
180
|
+
|
|
181
|
+
The AS/400 adapter uses SSH keepalive (`keepaliveInterval: 30000` ×
|
|
182
|
+
`keepaliveCountMax: 3` = 90 s tolerance) and auto-reconnects on `'close'` with
|
|
183
|
+
exponential backoff:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
1 s → 2 s → 4 s → 8 s → 16 s → 32 s → 60 s (cap)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Backoff resets to 1 s on successful re-auth (`'ready'` event).
|
|
190
|
+
|
|
191
|
+
To verify reconnect health: tail the agent log; `'close'` events should be
|
|
192
|
+
followed by a successful `'ready'` within ~60 s. If reconnect storms occur:
|
|
193
|
+
|
|
194
|
+
- Verify the AS/400 SSH server's idle timeout setting. Some SSH daemons close
|
|
195
|
+
sessions despite keepalive; tune the daemon's `ClientAliveInterval` higher
|
|
196
|
+
than 30 s.
|
|
197
|
+
- Check network MTU between the agent and AS/400. Fragmented keepalive packets
|
|
198
|
+
get dropped.
|
|
199
|
+
- The polling cadence (`pollInterval`, default 30 s) is independent of the
|
|
200
|
+
keepalive cadence — they run on separate timers.
|
|
201
|
+
|
|
202
|
+
### `npm run sync-fixtures` fails with "GitHub credentials not configured"
|
|
203
|
+
|
|
204
|
+
Confirm `~/.config/visionhammer/github-pat` exists, is readable by the agent
|
|
205
|
+
user, and contains a valid PAT with `repo` scope. The script reads this file
|
|
206
|
+
once at invocation; it never accepts a PAT from env vars. (T-2-04 mitigation.)
|
|
207
|
+
|
|
208
|
+
### `npm run sync-fixtures` fails with "PAT validation failed"
|
|
209
|
+
|
|
210
|
+
The PAT validity gate (`GET /user`) returned a non-200 status. Causes:
|
|
211
|
+
|
|
212
|
+
- PAT expired or revoked. Regenerate at github.com/settings/tokens.
|
|
213
|
+
- PAT lost `repo` scope. Regenerate.
|
|
214
|
+
- Network failure to api.github.com. Re-run when connectivity returns.
|
|
215
|
+
|
|
216
|
+
### `npm run sync-fixtures` partial failure for one stack
|
|
217
|
+
|
|
218
|
+
The script aborts on first failure (per current implementation) so each clone
|
|
219
|
+
gates the next. Common causes:
|
|
220
|
+
|
|
221
|
+
- Repo deleted from `williambella/visionhammer-example-<stack>`. Recreate by
|
|
222
|
+
re-running Phase 2 / Plan 02-0X (where X = 2..6).
|
|
223
|
+
- Stack name typo in `--stacks=`. Allowed values:
|
|
224
|
+
`oracle-forms,plsql,sap-abap,cobol,ibm-mq`.
|
|
225
|
+
- Network failure mid-clone. Re-run the script.
|
|
226
|
+
|
|
227
|
+
### SIGTERM doesn't shut down adapters cleanly
|
|
228
|
+
|
|
229
|
+
The `SIGTERM` handler in `src/server.js` calls `stopAll(_legacyAdapters, 5000)`
|
|
230
|
+
— adapters have 5 s to release file handles + close SSH. The 5-s budget is
|
|
231
|
+
enforced by an `unref`'d timer racing the `Promise.all` of per-adapter
|
|
232
|
+
`stop()` calls; per-adapter exceptions are swallowed so the shutdown path
|
|
233
|
+
always returns.
|
|
234
|
+
|
|
235
|
+
If a process refuses to exit altogether, that's a separate concern: the
|
|
236
|
+
`local-agent` process keeps an `express` listener (`PORT`) and an MCP HTTP
|
|
237
|
+
listener (`MCP_PORT`) open, plus an optional WebSocket reverse tunnel. Those
|
|
238
|
+
are not closed by the SIGTERM handler today (pre-existing local-agent
|
|
239
|
+
architecture). Set `DISABLE_WS_TUNNEL=true` to drop the WS half; the express
|
|
240
|
+
+ MCP listeners require a separate explicit close path.
|
|
241
|
+
|
|
242
|
+
For the **legacy connector contract specifically** — `stopAll(adapters, 5000)`
|
|
243
|
+
is verified to resolve within 5 s (registry contract test
|
|
244
|
+
`stopAll respects timeout — slow stop does not block forever`).
|
|
245
|
+
|
|
246
|
+
### Adapter logs nothing
|
|
247
|
+
|
|
248
|
+
Possible causes by adapter:
|
|
249
|
+
|
|
250
|
+
- **All chokidar adapters** — TAIL mode is the default. Newly-appended lines
|
|
251
|
+
emit; existing content does not. To replay a file from byte 0 set
|
|
252
|
+
`replayOnStart: true` in the adapter config.
|
|
253
|
+
- **SAP / IBM MQ / Oracle Forms / COBOL** — verify the watched directory
|
|
254
|
+
exists; ENOENT is silent by design (legitimate state for newly-provisioned
|
|
255
|
+
systems).
|
|
256
|
+
- **IBM MQ** — only `AMQERR01.LOG`, `AMQERR02.LOG`, `AMQERR03.LOG` are read.
|
|
257
|
+
`AMQERR04.LOG` and other names are silently dropped at the in-handler anchor
|
|
258
|
+
regex.
|
|
259
|
+
- **AS/400** — verify SSH connectivity (`ssh -p 22 user@as400-prd-01 'system DSPLOG'`).
|
|
260
|
+
ENOENT-equivalent (unreachable host) shows in the agent stderr only on the
|
|
261
|
+
first `'close'` failure; subsequent reconnect attempts are silent.
|
|
262
|
+
|
|
263
|
+
## Field references
|
|
264
|
+
|
|
265
|
+
- 02-SPEC.md: `~/Documents/Projects/visionhammer-agents/.planning/phases/02-legacy-stack-support/02-SPEC.md`
|
|
266
|
+
- Phase 3 (LegacyErrorPatterns Java catalog): consumes `test/fixtures/<stack>/`
|
|
267
|
+
as test fixtures.
|
|
268
|
+
- Phase 6 (E2E smoke): re-uses the example repos as workspaces.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepdebug-local-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
4
4
|
"description": "DeepDebug Local Agent - AI-powered code debugging assistant",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"docker:build": "docker build -t deepdebug/local-agent:latest .",
|
|
23
23
|
"docker:push": "docker push deepdebug/local-agent:latest",
|
|
24
24
|
"test": "node --test",
|
|
25
|
+
"sync-fixtures": "node scripts/sync-fixtures.mjs",
|
|
25
26
|
"lint": "eslint src/"
|
|
26
27
|
},
|
|
27
28
|
"pkg": {
|
|
@@ -34,12 +35,14 @@
|
|
|
34
35
|
"@anthropic-ai/sdk": "^0.39.0",
|
|
35
36
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
36
37
|
"body-parser": "^1.20.3",
|
|
38
|
+
"chokidar": "^3.6.0",
|
|
37
39
|
"cors": "^2.8.5",
|
|
38
40
|
"express": "^4.19.2",
|
|
39
41
|
"js-yaml": "^4.1.0",
|
|
40
42
|
"pg": "^8.20.0",
|
|
41
43
|
"pidusage": "^3.0.2",
|
|
42
44
|
"properties-reader": "^2.3.0",
|
|
45
|
+
"ssh2": "^1.16.0",
|
|
43
46
|
"strip-ansi": "^7.1.0",
|
|
44
47
|
"unidiff": "^1.0.2",
|
|
45
48
|
"ws": "^8.18.0"
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/sync-fixtures.mjs — Phase 2 fixture sync (Plan 02-12 full implementation).
|
|
3
|
+
//
|
|
4
|
+
// For each requested stack, clones williambella/visionhammer-example-<stack>
|
|
5
|
+
// shallow (--depth 1, --branch main) into /tmp/sync-<stack>, copies samples/
|
|
6
|
+
// log files into test/fixtures/<adapter-dir>/ (with IBM MQ filename mapping),
|
|
7
|
+
// then deletes the temp clone. Idempotent: re-runs cleanly overwrite prior
|
|
8
|
+
// fixtures and rewipe /tmp scratch dirs.
|
|
9
|
+
//
|
|
10
|
+
// SECURITY (T-2-04 — PAT discipline):
|
|
11
|
+
// - PAT read ONCE from ~/.config/visionhammer/github-pat.
|
|
12
|
+
// - Embedded ONLY in `https://x-access-token:$PAT@github.com/...` URL form,
|
|
13
|
+
// passed as a `child_process.spawn` ARGV element (not a shell string), so
|
|
14
|
+
// no shell ever sees it. The argv carrying the URL IS visible to ps(1)
|
|
15
|
+
// during clone — that is the standard git-over-https risk window per AD-3.
|
|
16
|
+
// - Stdout/stderr are CAPTURED but only stack names / generic progress are
|
|
17
|
+
// written to console. Git's own stderr (which can contain the embedded
|
|
18
|
+
// URL on auth failure) is sanitised: every literal occurrence of the PAT
|
|
19
|
+
// is replaced with `***REDACTED***` before the buffer touches stderr.
|
|
20
|
+
// - The PAT NEVER goes into argv via a shell string, environment variable,
|
|
21
|
+
// log file, or thrown error message.
|
|
22
|
+
//
|
|
23
|
+
// SECURITY (T-2-05 — PAT scope assertion):
|
|
24
|
+
// - Before any clone, calls GitHub /user (with the PAT) to confirm the token
|
|
25
|
+
// authenticates. We do NOT enforce a specific scope here because the
|
|
26
|
+
// fine-grained-PAT scope vocabulary differs from classic-PAT scopes; a
|
|
27
|
+
// successful GET /user is sufficient to prove the token is valid and the
|
|
28
|
+
// token cannot exceed `repo`-equivalent scope per the project setup
|
|
29
|
+
// contract documented in docs/legacy-connectors.md.
|
|
30
|
+
//
|
|
31
|
+
// SECURITY (T-2-06 — fixture quarantine):
|
|
32
|
+
// - test/fixtures/*/ is .gitignore'd at the repo root (verified by Plan 02-01).
|
|
33
|
+
// The synced files are therefore quarantined: they NEVER enter version
|
|
34
|
+
// control of deepdebug-local-agent. AD-2 explicit submodule rejection.
|
|
35
|
+
|
|
36
|
+
import {
|
|
37
|
+
readFileSync,
|
|
38
|
+
mkdirSync,
|
|
39
|
+
rmSync,
|
|
40
|
+
readdirSync,
|
|
41
|
+
copyFileSync,
|
|
42
|
+
existsSync,
|
|
43
|
+
} from 'node:fs';
|
|
44
|
+
import { spawn } from 'node:child_process';
|
|
45
|
+
import { homedir } from 'node:os';
|
|
46
|
+
import { join, dirname, basename } from 'node:path';
|
|
47
|
+
import { fileURLToPath } from 'node:url';
|
|
48
|
+
|
|
49
|
+
const PAT_PATH = join(homedir(), '.config', 'visionhammer', 'github-pat');
|
|
50
|
+
const ALLOWED_STACKS = ['oracle-forms', 'plsql', 'sap-abap', 'cobol', 'ibm-mq'];
|
|
51
|
+
|
|
52
|
+
// Mapping: example-repo-stack name → local fixture directory name.
|
|
53
|
+
// Example repos use product names (sap-abap, ibm-mq); local fixtures use
|
|
54
|
+
// adapter names (sap, ibmmq). plsql has no Phase 2 adapter — fixtures are
|
|
55
|
+
// synced for Phase 3 catalog work.
|
|
56
|
+
const FIXTURE_DIR_MAP = Object.freeze({
|
|
57
|
+
'oracle-forms': 'oracle-forms',
|
|
58
|
+
'plsql': 'plsql',
|
|
59
|
+
'sap-abap': 'sap',
|
|
60
|
+
'cobol': 'cobol',
|
|
61
|
+
'ibm-mq': 'ibmmq',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// IBM MQ filename mapping (per Plan 02-08 SUMMARY's filename-mismatch decision).
|
|
65
|
+
// The IbmMqLogAdapter only reads files matching `^AMQERR0[1-3]\.LOG$`. The
|
|
66
|
+
// remote example repo intentionally uses descriptive sample names; sync-time
|
|
67
|
+
// renaming makes the synced fixtures consumable by the adapter's contract.
|
|
68
|
+
// Order is stable so re-runs always produce the same target filenames.
|
|
69
|
+
const IBM_MQ_FILENAME_MAP = Object.freeze({
|
|
70
|
+
'ibm-mq-sample-mqrc-2033.log': 'AMQERR01.LOG',
|
|
71
|
+
'ibm-mq-sample-mqrc-2058.log': 'AMQERR02.LOG',
|
|
72
|
+
'ibm-mq-sample-resource-leak.log': 'AMQERR03.LOG',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
76
|
+
const REPO_ROOT = join(__dirname, '..');
|
|
77
|
+
|
|
78
|
+
function readPat() {
|
|
79
|
+
// SECURITY: do NOT log PAT_PATH contents on error or success. ENOENT = caller missing setup.
|
|
80
|
+
try {
|
|
81
|
+
return readFileSync(PAT_PATH, 'utf8').trim();
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (err.code === 'ENOENT') {
|
|
84
|
+
process.stderr.write(
|
|
85
|
+
'sync-fixtures: GitHub credentials not configured. See docs/legacy-connectors.md.\n',
|
|
86
|
+
);
|
|
87
|
+
process.exit(2);
|
|
88
|
+
}
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseArgs(argv) {
|
|
94
|
+
// --stacks=oracle-forms,plsql OR --stacks oracle-forms plsql OR default = all
|
|
95
|
+
const args = argv.slice(2);
|
|
96
|
+
const flagIdx = args.findIndex((a) => a === '--stacks' || a.startsWith('--stacks='));
|
|
97
|
+
if (flagIdx === -1) return ALLOWED_STACKS;
|
|
98
|
+
const flag = args[flagIdx];
|
|
99
|
+
const raw = flag.startsWith('--stacks=')
|
|
100
|
+
? flag.slice('--stacks='.length)
|
|
101
|
+
: (args[flagIdx + 1] || '');
|
|
102
|
+
const requested = raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
103
|
+
const unknown = requested.filter((s) => !ALLOWED_STACKS.includes(s));
|
|
104
|
+
if (unknown.length > 0) {
|
|
105
|
+
process.stderr.write(
|
|
106
|
+
`sync-fixtures: unknown stack(s): ${unknown.join(', ')}. Allowed: ${ALLOWED_STACKS.join(', ')}\n`,
|
|
107
|
+
);
|
|
108
|
+
process.exit(2);
|
|
109
|
+
}
|
|
110
|
+
return requested.length === 0 ? ALLOWED_STACKS : requested;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Run a child process; never log argv. Captures stdout/stderr buffers.
|
|
115
|
+
* @returns {Promise<{code:number, out:string, err:string}>}
|
|
116
|
+
*/
|
|
117
|
+
function run(cmd, args, cwd) {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const child = spawn(cmd, args, {
|
|
120
|
+
cwd,
|
|
121
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
122
|
+
});
|
|
123
|
+
let out = '';
|
|
124
|
+
let err = '';
|
|
125
|
+
child.stdout.on('data', (d) => { out += d.toString(); });
|
|
126
|
+
child.stderr.on('data', (d) => { err += d.toString(); });
|
|
127
|
+
child.on('close', (code) => resolve({ code, out, err }));
|
|
128
|
+
child.on('error', reject);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* T-2-05: confirm the PAT authenticates against GitHub before any clone.
|
|
134
|
+
* On failure, exit 2 with a generic message — never echo the PAT or response.
|
|
135
|
+
*/
|
|
136
|
+
async function assertPatValid(pat) {
|
|
137
|
+
// node:fetch is built-in on Node 18+. Authorization header is the standard
|
|
138
|
+
// PAT delivery channel — never logged, never persisted.
|
|
139
|
+
const res = await fetch('https://api.github.com/user', {
|
|
140
|
+
headers: {
|
|
141
|
+
Authorization: `Bearer ${pat}`,
|
|
142
|
+
Accept: 'application/vnd.github+json',
|
|
143
|
+
'User-Agent': 'visionhammer-sync-fixtures',
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
if (!res.ok) {
|
|
147
|
+
// Do NOT print response body — GitHub may echo parts of the request URL.
|
|
148
|
+
process.stderr.write(
|
|
149
|
+
`sync-fixtures: PAT authentication failed (HTTP ${res.status}). Verify ~/.config/visionhammer/github-pat.\n`,
|
|
150
|
+
);
|
|
151
|
+
process.exit(2);
|
|
152
|
+
}
|
|
153
|
+
// We intentionally do NOT verify scope strings — fine-grained-PAT and
|
|
154
|
+
// classic-PAT report scopes differently. Validity is sufficient per AD-3.
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function syncStack(stack, pat) {
|
|
158
|
+
const tmpClone = `/tmp/sync-${stack}`;
|
|
159
|
+
const repoUrl = `https://x-access-token:${pat}@github.com/williambella/visionhammer-example-${stack}.git`;
|
|
160
|
+
const fixtureDir = FIXTURE_DIR_MAP[stack];
|
|
161
|
+
const dest = join(REPO_ROOT, 'test', 'fixtures', fixtureDir);
|
|
162
|
+
|
|
163
|
+
// Clean any prior temp clone (idempotent re-runs).
|
|
164
|
+
try { rmSync(tmpClone, { recursive: true, force: true }); } catch (_) { /* ok */ }
|
|
165
|
+
|
|
166
|
+
process.stdout.write(`sync-fixtures: cloning ${stack} ... `);
|
|
167
|
+
const clone = await run(
|
|
168
|
+
'git',
|
|
169
|
+
['clone', '--depth', '1', '--branch', 'main', repoUrl, tmpClone],
|
|
170
|
+
REPO_ROOT,
|
|
171
|
+
);
|
|
172
|
+
if (clone.code !== 0) {
|
|
173
|
+
process.stderr.write(
|
|
174
|
+
`\nsync-fixtures: git clone failed for ${stack} (exit ${clone.code}).\n`,
|
|
175
|
+
);
|
|
176
|
+
// Sanitize: replace any occurrence of the PAT with a redaction before
|
|
177
|
+
// letting git's stderr touch our stderr. Defence in depth — git rarely
|
|
178
|
+
// includes the credential portion of the URL but the redaction is cheap.
|
|
179
|
+
const sanitized = clone.err.split(pat).join('***REDACTED***');
|
|
180
|
+
process.stderr.write(sanitized + '\n');
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
process.stdout.write('cloned. ');
|
|
184
|
+
|
|
185
|
+
const srcSamples = join(tmpClone, 'samples');
|
|
186
|
+
if (!existsSync(srcSamples)) {
|
|
187
|
+
process.stderr.write(
|
|
188
|
+
`\nsync-fixtures: ${stack}: samples/ dir missing in repo, skipping copy.\n`,
|
|
189
|
+
);
|
|
190
|
+
try { rmSync(tmpClone, { recursive: true, force: true }); } catch (_) { /* ok */ }
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
mkdirSync(dest, { recursive: true });
|
|
195
|
+
const files = readdirSync(srcSamples).filter(
|
|
196
|
+
(f) => f.toLowerCase().endsWith('.log'),
|
|
197
|
+
);
|
|
198
|
+
let copied = 0;
|
|
199
|
+
for (const f of files) {
|
|
200
|
+
let targetName = f;
|
|
201
|
+
if (stack === 'ibm-mq') {
|
|
202
|
+
// Apply filename mapping (Plan 02-08 SUMMARY decision).
|
|
203
|
+
targetName = IBM_MQ_FILENAME_MAP[f] ?? null;
|
|
204
|
+
if (!targetName) {
|
|
205
|
+
// Sample without an explicit mapping is silently skipped — adapter
|
|
206
|
+
// would not pick it up anyway (filename anchor /^AMQERR0[1-3]\.LOG$/).
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
copyFileSync(join(srcSamples, f), join(dest, targetName));
|
|
211
|
+
copied++;
|
|
212
|
+
}
|
|
213
|
+
process.stdout.write(`copied ${copied} log(s) → test/fixtures/${fixtureDir}/. `);
|
|
214
|
+
|
|
215
|
+
// Cleanup temp clone.
|
|
216
|
+
try { rmSync(tmpClone, { recursive: true, force: true }); } catch (_) { /* ok */ }
|
|
217
|
+
process.stdout.write('done.\n');
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function main() {
|
|
222
|
+
const stacks = parseArgs(process.argv);
|
|
223
|
+
const pat = readPat();
|
|
224
|
+
await assertPatValid(pat); // T-2-05 gate
|
|
225
|
+
let failures = 0;
|
|
226
|
+
for (const s of stacks) {
|
|
227
|
+
const ok = await syncStack(s, pat);
|
|
228
|
+
if (!ok) failures++;
|
|
229
|
+
}
|
|
230
|
+
// PAT goes out of scope here — never persisted, never logged.
|
|
231
|
+
if (failures > 0) {
|
|
232
|
+
process.stderr.write(
|
|
233
|
+
`sync-fixtures: ${failures}/${stacks.length} stack(s) failed.\n`,
|
|
234
|
+
);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
process.stdout.write(`sync-fixtures: all ${stacks.length} stack(s) synced.\n`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
main().catch((err) => {
|
|
241
|
+
// err.message MUST not contain PAT. We never interpolate the PAT into thrown
|
|
242
|
+
// errors anywhere above; the only thrown paths are fs/network errors whose
|
|
243
|
+
// messages do not reference the credential.
|
|
244
|
+
process.stderr.write(`sync-fixtures: fatal: ${err.message}\n`);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/adapters/as400/as400-parser.js
|
|
2
|
+
// OS/400 message ID prefixes: CPF (escape), MCH (machine check), SQL (DB2 for i)
|
|
3
|
+
// Anchored at start-of-line so arbitrary text containing the substring
|
|
4
|
+
// (e.g. "see CPF1234 docs") does not match — T-2-03-equivalent isolation surface.
|
|
5
|
+
export const AS400_MSGID_REGEX = /^(CPF|MCH|SQL)(\d{4})\b/;
|
|
6
|
+
|
|
7
|
+
export function extractMessageId(line) {
|
|
8
|
+
const m = line.match(AS400_MSGID_REGEX);
|
|
9
|
+
return m ? `${m[1]}${m[2]}` : null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const DSPLOG_COMMAND = 'system "DSPLOG OUTPUT(*PRINT) LOGTYPE(*MSG) MSGTYPE(*ESCAPE *NOTIFY)"';
|