delimit-cli 2.3.1 → 2.4.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/CHANGELOG.md +33 -0
- package/README.md +81 -109
- package/adapters/codex-forge.js +107 -0
- package/adapters/codex-jamsons.js +142 -0
- package/adapters/codex-security.js +94 -0
- package/adapters/gemini-forge.js +109 -0
- package/bin/delimit-cli.js +70 -11
- package/package.json +2 -2
- package/tests/cli.test.js +359 -0
- package/tests/fixtures/openapi-changed.yaml +56 -0
- package/tests/fixtures/openapi.yaml +87 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [2.4.0] - 2026-03-15
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- 29 real CLI tests covering init, lint, diff, explain, doctor, presets, and error handling
|
|
7
|
+
- Auto-write GitHub Actions workflow file on `delimit init`
|
|
8
|
+
|
|
9
|
+
### Improved
|
|
10
|
+
- Version now read from package.json instead of hardcoded
|
|
11
|
+
- Error handling across all commands
|
|
12
|
+
|
|
13
|
+
## [2.3.2] - 2026-03-09
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Clean --help output (legacy commands hidden)
|
|
17
|
+
- File existence checks before lint/diff operations
|
|
18
|
+
- --policy flag accepts preset names (strict, default, relaxed)
|
|
19
|
+
|
|
20
|
+
## [2.3.0] - 2026-03-07
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- Policy presets: strict (all errors), default (balanced), relaxed (warnings only)
|
|
24
|
+
- `delimit doctor` command for environment diagnostics
|
|
25
|
+
- `delimit explain` command with 7 output templates
|
|
26
|
+
|
|
27
|
+
## [2.0.0] - 2026-02-28
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- Deterministic diff engine (23 change types, 10 breaking)
|
|
31
|
+
- Policy enforcement with exit code 1 on violations
|
|
32
|
+
- Semver classification (MAJOR/MINOR/PATCH/NONE)
|
|
33
|
+
- Zero-Spec extraction for FastAPI, NestJS, Express
|
package/README.md
CHANGED
|
@@ -1,141 +1,108 @@
|
|
|
1
|
-
# delimit
|
|
1
|
+
# delimit
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Deterministic diff engine + policy enforcement + semver classification for OpenAPI specs. The independent successor to Optic.
|
|
3
|
+
Catch breaking API changes before they ship.
|
|
6
4
|
|
|
7
5
|
[](https://www.npmjs.com/package/delimit-cli)
|
|
6
|
+
[](https://github.com/marketplace/actions/delimit-api-governance)
|
|
8
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](#)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Deterministic diff engine for OpenAPI specs. Detects breaking changes, classifies semver, enforces policy, and posts PR comments with migration guides. No API keys, no external services.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
npm install -g delimit-cli
|
|
14
|
-
```
|
|
12
|
+
---
|
|
15
13
|
|
|
16
|
-
##
|
|
14
|
+
## GitHub Action (recommended)
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
# 1. Initialize with a policy preset
|
|
20
|
-
delimit init --preset default
|
|
16
|
+
Add `.github/workflows/api-check.yml`:
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
```yaml
|
|
19
|
+
name: API Contract Check
|
|
20
|
+
on: pull_request
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
jobs:
|
|
23
|
+
delimit:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
permissions:
|
|
26
|
+
pull-requests: write
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
with:
|
|
31
|
+
ref: ${{ github.event.pull_request.base.sha }}
|
|
32
|
+
path: base
|
|
33
|
+
- uses: delimit-ai/delimit-action@v1
|
|
34
|
+
with:
|
|
35
|
+
old_spec: base/api/openapi.yaml
|
|
36
|
+
new_spec: api/openapi.yaml
|
|
27
37
|
```
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Delimit deterministically detects 23 types of API changes, including 10 breaking patterns:
|
|
32
|
-
|
|
33
|
-
- Endpoint or method removal
|
|
34
|
-
- Required parameter addition
|
|
35
|
-
- Response field removal
|
|
36
|
-
- Type changes
|
|
37
|
-
- Enum value removal
|
|
38
|
-
- And more
|
|
39
|
-
|
|
40
|
-
Every change is classified as `MAJOR`, `MINOR`, `PATCH`, or `NONE` per semver.
|
|
41
|
-
|
|
42
|
-
## Commands
|
|
43
|
-
|
|
44
|
-
| Command | Description |
|
|
45
|
-
|---------|-------------|
|
|
46
|
-
| `delimit init` | Create `.delimit/policies.yml` with a policy preset |
|
|
47
|
-
| `delimit lint <old> <new>` | Diff + policy check — returns exit code 1 on violations |
|
|
48
|
-
| `delimit diff <old> <new>` | Raw diff with `[BREAKING]`/`[safe]` tags |
|
|
49
|
-
| `delimit explain <old> <new>` | Human-readable change explanation |
|
|
39
|
+
Runs in **advisory mode** by default -- posts a PR comment but never fails your build. Set `mode: enforce` when you are ready to block merges on breaking changes.
|
|
50
40
|
|
|
51
|
-
|
|
41
|
+
---
|
|
52
42
|
|
|
53
|
-
|
|
43
|
+
## CLI
|
|
54
44
|
|
|
55
45
|
```bash
|
|
56
|
-
delimit
|
|
57
|
-
delimit
|
|
58
|
-
delimit
|
|
46
|
+
npx delimit-cli lint api/openapi.yaml
|
|
47
|
+
npx delimit-cli diff old.yaml new.yaml
|
|
48
|
+
npx delimit-cli explain old.yaml new.yaml --template migration
|
|
59
49
|
```
|
|
60
50
|
|
|
61
|
-
|
|
62
|
-
|--------|-----------------|--------------|---------------|
|
|
63
|
-
| `strict` | Error (blocks) | Error (blocks) | Error (blocks) |
|
|
64
|
-
| `default` | Error (blocks) | Warning | Error (blocks) |
|
|
65
|
-
| `relaxed` | Warning | Warning | Info |
|
|
66
|
-
|
|
67
|
-
Pass a preset directly to lint:
|
|
51
|
+
Or install globally:
|
|
68
52
|
|
|
69
53
|
```bash
|
|
70
|
-
|
|
54
|
+
npm install -g delimit-cli
|
|
55
|
+
delimit init --preset default
|
|
56
|
+
delimit lint old.yaml new.yaml
|
|
71
57
|
```
|
|
72
58
|
|
|
73
|
-
|
|
59
|
+
### Commands
|
|
74
60
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
delimit
|
|
61
|
+
| Command | What it does |
|
|
62
|
+
|---------|-------------|
|
|
63
|
+
| `delimit init [--preset]` | Create `.delimit/policies.yml` |
|
|
64
|
+
| `delimit lint <old> <new>` | Diff + policy check. Exit 1 on violations. |
|
|
65
|
+
| `delimit diff <old> <new>` | Raw diff with `[BREAKING]` / `[safe]` tags |
|
|
66
|
+
| `delimit explain <old> <new>` | Human-readable explanation (7 templates) |
|
|
78
67
|
|
|
79
|
-
|
|
80
|
-
delimit explain old.yaml new.yaml -t migration
|
|
81
|
-
delimit explain old.yaml new.yaml -t pr_comment
|
|
82
|
-
delimit explain old.yaml new.yaml -t changelog
|
|
68
|
+
---
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
delimit lint old.yaml new.yaml --json
|
|
86
|
-
```
|
|
70
|
+
## What it catches
|
|
87
71
|
|
|
88
|
-
|
|
72
|
+
10 breaking change types, detected deterministically:
|
|
89
73
|
|
|
90
|
-
|
|
|
91
|
-
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
| `
|
|
97
|
-
|
|
|
98
|
-
| `
|
|
74
|
+
| Breaking change | Example |
|
|
75
|
+
|----------------|---------|
|
|
76
|
+
| Endpoint removed | `DELETE /users/{id}` path deleted |
|
|
77
|
+
| Method removed | `PATCH` dropped from `/orders` |
|
|
78
|
+
| Required parameter added | New required query param on existing endpoint |
|
|
79
|
+
| Parameter removed | `?filter` param deleted |
|
|
80
|
+
| Response removed | `200` response code dropped |
|
|
81
|
+
| Required field added | New required field in request body |
|
|
82
|
+
| Response field removed | `email` field removed from response |
|
|
83
|
+
| Type changed | `age` changed from `string` to `integer` |
|
|
84
|
+
| Format changed | `date` changed to `date-time` |
|
|
85
|
+
| Enum value removed | `status: "pending"` no longer allowed |
|
|
99
86
|
|
|
100
|
-
|
|
87
|
+
Plus 7 non-breaking types (endpoint added, optional field added, etc.) for full change visibility. Every change is classified as `MAJOR`, `MINOR`, `PATCH`, or `NONE`.
|
|
101
88
|
|
|
102
|
-
|
|
89
|
+
---
|
|
103
90
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
permissions:
|
|
111
|
-
contents: read
|
|
112
|
-
pull-requests: write
|
|
113
|
-
jobs:
|
|
114
|
-
api-governance:
|
|
115
|
-
runs-on: ubuntu-latest
|
|
116
|
-
steps:
|
|
117
|
-
- uses: actions/checkout@v4
|
|
118
|
-
- uses: actions/checkout@v4
|
|
119
|
-
with:
|
|
120
|
-
ref: ${{ github.event.pull_request.base.sha }}
|
|
121
|
-
path: _base
|
|
122
|
-
- uses: delimit-ai/delimit@v1
|
|
123
|
-
with:
|
|
124
|
-
old_spec: _base/path/to/openapi.yaml
|
|
125
|
-
new_spec: path/to/openapi.yaml
|
|
126
|
-
mode: advisory # or 'enforce' to block PRs
|
|
91
|
+
## Policy presets
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
delimit init --preset strict # All breaking changes are errors. For public/payment APIs.
|
|
95
|
+
delimit init --preset default # Breaking changes error, type changes warn. For most teams.
|
|
96
|
+
delimit init --preset relaxed # Everything is a warning. For internal APIs and prototyping.
|
|
127
97
|
```
|
|
128
98
|
|
|
129
|
-
|
|
130
|
-
- Semver badge (`MAJOR` / `MINOR` / `PATCH`)
|
|
131
|
-
- Violation table with severity
|
|
132
|
-
- Expandable migration guide for breaking changes
|
|
99
|
+
Or pass inline: `delimit lint --policy strict old.yaml new.yaml`
|
|
133
100
|
|
|
134
|
-
|
|
101
|
+
---
|
|
135
102
|
|
|
136
|
-
## Custom
|
|
103
|
+
## Custom policies
|
|
137
104
|
|
|
138
|
-
Create `.delimit/policies.yml
|
|
105
|
+
Create `.delimit/policies.yml`:
|
|
139
106
|
|
|
140
107
|
```yaml
|
|
141
108
|
override_defaults: false
|
|
@@ -151,17 +118,22 @@ rules:
|
|
|
151
118
|
message: "V1 API is frozen. Make changes in V2."
|
|
152
119
|
```
|
|
153
120
|
|
|
154
|
-
|
|
121
|
+
---
|
|
155
122
|
|
|
156
|
-
|
|
123
|
+
## Supported formats
|
|
124
|
+
|
|
125
|
+
- OpenAPI 3.0 and 3.1
|
|
157
126
|
- Swagger 2.0
|
|
158
|
-
- YAML and JSON
|
|
127
|
+
- YAML and JSON
|
|
128
|
+
|
|
129
|
+
---
|
|
159
130
|
|
|
160
131
|
## Links
|
|
161
132
|
|
|
162
|
-
- [
|
|
163
|
-
- [GitHub](https://github.com/delimit-
|
|
164
|
-
- [
|
|
133
|
+
- [delimit.ai](https://delimit.ai) -- Project home
|
|
134
|
+
- [GitHub Action on Marketplace](https://github.com/marketplace/actions/delimit-api-governance) -- Install in one click
|
|
135
|
+
- [delimit-cli on npm](https://www.npmjs.com/package/delimit-cli) -- CLI package
|
|
136
|
+
- [Quickstart repo](https://github.com/delimit-ai/delimit-quickstart) -- Try it in 2 minutes
|
|
165
137
|
|
|
166
138
|
## License
|
|
167
139
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Delimit™ Codex Forge Adapter
|
|
4
|
+
* Layer: Forge (execution governance)
|
|
5
|
+
* Surfaces test failures, deploy state, and release gates before accepting code.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const axios = require('axios');
|
|
11
|
+
const AGENT_URL = `http://127.0.0.1:${process.env.DELIMIT_AGENT_PORT || 7823}`;
|
|
12
|
+
|
|
13
|
+
class DelimitCodexForge {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.name = 'delimit-forge';
|
|
16
|
+
this.version = '1.0.0';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Before accepting code: check test gate and deploy state.
|
|
21
|
+
*/
|
|
22
|
+
async onBeforeSuggestion(context) {
|
|
23
|
+
const [testState, deployState] = await Promise.allSettled([
|
|
24
|
+
this._getTestGate(),
|
|
25
|
+
this._getDeployState(),
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const warnings = [];
|
|
29
|
+
|
|
30
|
+
if (testState.status === 'fulfilled' && testState.value.failing > 0) {
|
|
31
|
+
warnings.push(`[FORGE] ${testState.value.failing} test(s) failing — fix before accepting`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (deployState.status === 'fulfilled' && deployState.value.locked) {
|
|
35
|
+
warnings.push(`[FORGE] Deploy locked: ${deployState.value.reason || 'release in progress'}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (warnings.length > 0) {
|
|
39
|
+
return { allow: true, warning: warnings.join(' | ') };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { allow: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async onAfterAccept(context) {
|
|
46
|
+
try {
|
|
47
|
+
await axios.post(`${AGENT_URL}/audit`, {
|
|
48
|
+
action: 'forge_accept',
|
|
49
|
+
context,
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
}, { timeout: 2000 });
|
|
52
|
+
} catch (_) { /* silent */ }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async _getTestGate() {
|
|
56
|
+
const r = await axios.get(`${AGENT_URL}/test/status`, { timeout: 3000 });
|
|
57
|
+
return r.data;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async _getDeployState() {
|
|
61
|
+
const r = await axios.get(`${AGENT_URL}/deploy/status`, { timeout: 3000 });
|
|
62
|
+
return r.data;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async handleCommand(command, _args) {
|
|
66
|
+
const { execSync } = require('child_process');
|
|
67
|
+
const cmds = {
|
|
68
|
+
'forge': 'delimit status --layer=forge',
|
|
69
|
+
'tests': 'delimit test --summary',
|
|
70
|
+
'deploy': 'delimit deploy --status',
|
|
71
|
+
'release': 'delimit release --status',
|
|
72
|
+
};
|
|
73
|
+
if (cmds[command]) {
|
|
74
|
+
try {
|
|
75
|
+
return execSync(cmds[command], { timeout: 10000 }).toString();
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return `[FORGE] Command failed: ${e.message}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Returns structured Forge context for consensus use.
|
|
84
|
+
*/
|
|
85
|
+
async getContext() {
|
|
86
|
+
const [tests, deploy, release] = await Promise.allSettled([
|
|
87
|
+
axios.get(`${AGENT_URL}/test/status`, { timeout: 3000 }),
|
|
88
|
+
axios.get(`${AGENT_URL}/deploy/status`, { timeout: 3000 }),
|
|
89
|
+
axios.get(`${AGENT_URL}/release/status`, { timeout: 3000 }),
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
layer: 'forge',
|
|
94
|
+
tests: tests.status === 'fulfilled' ? tests.value.data : null,
|
|
95
|
+
deploy: deploy.status === 'fulfilled' ? deploy.value.data : null,
|
|
96
|
+
release: release.status === 'fulfilled' ? release.value.data : null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
102
|
+
module.exports = new DelimitCodexForge();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (typeof registerSkill === 'function') {
|
|
106
|
+
registerSkill(new DelimitCodexForge());
|
|
107
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Delimit™ Codex Jamsons Adapter
|
|
4
|
+
* Layer: Jamsons OS (strategy + operational governance)
|
|
5
|
+
* Injects portfolio context, logs decisions, surfaces venture/priority state.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const axios = require('axios');
|
|
11
|
+
const AGENT_URL = `http://127.0.0.1:${process.env.DELIMIT_AGENT_PORT || 7823}`;
|
|
12
|
+
const JAMSONS_URL = `http://localhost:${process.env.JAMSONS_PORT || 8091}`;
|
|
13
|
+
|
|
14
|
+
const VENTURES = {
|
|
15
|
+
delimit: { priority: 'P0', status: 'active' },
|
|
16
|
+
domainvested: { priority: 'P2', status: 'maintenance' },
|
|
17
|
+
'wire.report': { priority: 'held', status: 'held' },
|
|
18
|
+
'livetube.ai': { priority: 'held', status: 'held' },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
class DelimitCodexJamsons {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.name = 'delimit-jamsons';
|
|
24
|
+
this.version = '1.0.0';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Before suggestion: inject portfolio context so the model has strategic awareness.
|
|
29
|
+
*/
|
|
30
|
+
async onBeforeSuggestion(context) {
|
|
31
|
+
try {
|
|
32
|
+
const portfolioCtx = await this._getPortfolioContext(context);
|
|
33
|
+
// Attach context metadata — Codex passes this through to the model
|
|
34
|
+
context._jamsons = portfolioCtx;
|
|
35
|
+
} catch (_) { /* fail open */ }
|
|
36
|
+
return { allow: true };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async onAfterAccept(context) {
|
|
40
|
+
// Log accepted suggestion to Jamsons decision ledger
|
|
41
|
+
try {
|
|
42
|
+
await axios.post(`${JAMSONS_URL}/api/chatops/decisions`, {
|
|
43
|
+
type: 'task',
|
|
44
|
+
title: `Codex: accepted suggestion in ${context.file || 'unknown file'}`,
|
|
45
|
+
detail: `Language: ${context.language || 'unknown'} | Tool: codex`,
|
|
46
|
+
user_id: 'codex-adapter',
|
|
47
|
+
tags: ['delimit', 'chatops'],
|
|
48
|
+
}, {
|
|
49
|
+
headers: { Authorization: `Bearer ${process.env.CHATOPS_AUTH_TOKEN}` },
|
|
50
|
+
timeout: 2000,
|
|
51
|
+
});
|
|
52
|
+
} catch (_) { /* silent */ }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async _getPortfolioContext(context) {
|
|
56
|
+
const [memory, decisions] = await Promise.allSettled([
|
|
57
|
+
this._searchMemory(context.file || context.language || 'delimit'),
|
|
58
|
+
this._getPendingDecisions(),
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
ventures: VENTURES,
|
|
63
|
+
activeVenture: this._detectVenture(context),
|
|
64
|
+
memory: memory.status === 'fulfilled' ? memory.value : [],
|
|
65
|
+
pendingDecisions: decisions.status === 'fulfilled' ? decisions.value : [],
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
_detectVenture(context) {
|
|
71
|
+
const path = (context.file || '').toLowerCase();
|
|
72
|
+
if (path.includes('delimit')) return 'delimit';
|
|
73
|
+
if (path.includes('domainvested')) return 'domainvested';
|
|
74
|
+
return 'delimit'; // default active venture
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async _searchMemory(query) {
|
|
78
|
+
const r = await axios.get(`${JAMSONS_URL}/api/memory/search`, {
|
|
79
|
+
params: { q: query, limit: 5 },
|
|
80
|
+
timeout: 2000,
|
|
81
|
+
});
|
|
82
|
+
return r.data?.results || [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async _getPendingDecisions() {
|
|
86
|
+
const r = await axios.get(`${JAMSONS_URL}/api/chatops/decisions`, {
|
|
87
|
+
params: { status: 'pending', limit: 5 },
|
|
88
|
+
headers: { Authorization: `Bearer ${process.env.CHATOPS_AUTH_TOKEN}` },
|
|
89
|
+
timeout: 2000,
|
|
90
|
+
});
|
|
91
|
+
return r.data?.decisions || r.data || [];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async handleCommand(command, _args) {
|
|
95
|
+
const { execSync } = require('child_process');
|
|
96
|
+
const cmds = {
|
|
97
|
+
'jamsons': 'delimit status --layer=jamsons',
|
|
98
|
+
'portfolio': 'delimit portfolio --summary',
|
|
99
|
+
'decisions': 'delimit decisions --pending',
|
|
100
|
+
'memory': 'delimit memory --recent',
|
|
101
|
+
};
|
|
102
|
+
if (cmds[command]) {
|
|
103
|
+
try {
|
|
104
|
+
return execSync(cmds[command], { timeout: 10000 }).toString();
|
|
105
|
+
} catch (e) {
|
|
106
|
+
return `[JAMSONS] Command failed: ${e.message}`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns structured Jamsons context for consensus use.
|
|
113
|
+
*/
|
|
114
|
+
async getContext() {
|
|
115
|
+
const [memory, decisions] = await Promise.allSettled([
|
|
116
|
+
axios.get(`${JAMSONS_URL}/api/memory/search`, {
|
|
117
|
+
params: { q: 'delimit strategy', limit: 5 },
|
|
118
|
+
timeout: 3000,
|
|
119
|
+
}),
|
|
120
|
+
axios.get(`${JAMSONS_URL}/api/chatops/decisions`, {
|
|
121
|
+
params: { status: 'pending' },
|
|
122
|
+
headers: { Authorization: `Bearer ${process.env.CHATOPS_AUTH_TOKEN}` },
|
|
123
|
+
timeout: 3000,
|
|
124
|
+
}),
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
layer: 'jamsons',
|
|
129
|
+
ventures: VENTURES,
|
|
130
|
+
memory: memory.status === 'fulfilled' ? (memory.value.data?.results || []) : null,
|
|
131
|
+
pendingDecisions: decisions.status === 'fulfilled' ? (decisions.value.data || []) : null,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
137
|
+
module.exports = new DelimitCodexJamsons();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (typeof registerSkill === 'function') {
|
|
141
|
+
registerSkill(new DelimitCodexJamsons());
|
|
142
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Delimit™ Codex Security Skill Adapter
|
|
4
|
+
* Layer: Delimit (code governance) — Security surface
|
|
5
|
+
* Triggered on pre-code-generation and pre-suggestion events
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const axios = require('axios');
|
|
11
|
+
const AGENT_URL = `http://127.0.0.1:${process.env.DELIMIT_AGENT_PORT || 7823}`;
|
|
12
|
+
|
|
13
|
+
class DelimitCodexSecurity {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.name = 'delimit-security';
|
|
16
|
+
this.version = '1.0.0';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async onBeforeSuggestion(context) {
|
|
20
|
+
try {
|
|
21
|
+
const { code, language, file } = context;
|
|
22
|
+
const response = await axios.post(`${AGENT_URL}/security`, {
|
|
23
|
+
action: 'codex_suggestion',
|
|
24
|
+
code,
|
|
25
|
+
language,
|
|
26
|
+
file,
|
|
27
|
+
tool: 'codex',
|
|
28
|
+
}, { timeout: 3000 });
|
|
29
|
+
|
|
30
|
+
const { severity, findings } = response.data;
|
|
31
|
+
|
|
32
|
+
if (severity === 'critical') {
|
|
33
|
+
return {
|
|
34
|
+
allow: false,
|
|
35
|
+
message: `[DELIMIT SECURITY] Blocked — critical finding: ${findings?.[0]?.message || 'see audit log'}`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (severity === 'high') {
|
|
40
|
+
return {
|
|
41
|
+
allow: true,
|
|
42
|
+
warning: `[DELIMIT SECURITY] High-severity finding: ${findings?.[0]?.message || 'review before accepting'}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { allow: true };
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (err.response?.data?.action === 'block') {
|
|
49
|
+
return { allow: false, message: `[DELIMIT SECURITY] ${err.response.data.reason}` };
|
|
50
|
+
}
|
|
51
|
+
// Fail open — security service unavailable
|
|
52
|
+
console.warn('[DELIMIT SECURITY] Scan unavailable:', err.message);
|
|
53
|
+
return { allow: true };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async onAfterAccept(context) {
|
|
58
|
+
try {
|
|
59
|
+
await axios.post(`${AGENT_URL}/audit`, {
|
|
60
|
+
action: 'codex_security_accept',
|
|
61
|
+
context,
|
|
62
|
+
timestamp: new Date().toISOString(),
|
|
63
|
+
}, { timeout: 2000 });
|
|
64
|
+
} catch (_) { /* silent */ }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async handleCommand(command, _args) {
|
|
68
|
+
if (command === 'security') {
|
|
69
|
+
const { execSync } = require('child_process');
|
|
70
|
+
try {
|
|
71
|
+
return execSync('delimit security --verbose', { timeout: 10000 }).toString();
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return `[DELIMIT SECURITY] Command failed: ${e.message}`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async getContext() {
|
|
79
|
+
try {
|
|
80
|
+
const r = await axios.get(`${AGENT_URL}/security/status`, { timeout: 3000 });
|
|
81
|
+
return { layer: 'delimit-security', status: 'ok', data: r.data };
|
|
82
|
+
} catch (_) {
|
|
83
|
+
return { layer: 'delimit-security', status: 'unavailable' };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
89
|
+
module.exports = new DelimitCodexSecurity();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (typeof registerSkill === 'function') {
|
|
93
|
+
registerSkill(new DelimitCodexSecurity());
|
|
94
|
+
}
|