@nerviq/cli 1.29.0 → 1.30.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 +1764 -1493
- package/README.md +568 -538
- package/SECURITY.md +78 -82
- package/bin/cli.js +2838 -2558
- package/docs/api-reference.md +356 -356
- package/docs/audit-fix.md +109 -0
- package/docs/autofix.md +3 -62
- package/docs/getting-started.md +1 -1
- package/docs/index.html +592 -592
- package/docs/integration-contracts.md +287 -287
- package/docs/maintenance.md +128 -128
- package/docs/new-platform-guide.md +202 -202
- package/docs/release-process.md +63 -0
- package/docs/shallow-risk.md +244 -244
- package/docs/why-nerviq.md +82 -82
- package/package.json +75 -67
- package/sdk/README.md +12 -3
- package/sdk/examples/langchain-integration.md +128 -0
- package/sdk/examples/self-governing-agent.js +135 -0
- package/sdk/index.d.ts +115 -0
- package/sdk/index.js +94 -0
- package/sdk/package.json +11 -0
- package/src/activity.js +13 -0
- package/src/aider/activity.js +226 -226
- package/src/aider/context.js +162 -162
- package/src/aider/freshness.js +123 -123
- package/src/aider/techniques.js +3465 -3465
- package/src/audit/layers.js +180 -180
- package/src/audit.js +1133 -1032
- package/src/auto-suggest.js +9 -2
- package/src/behavioral-drift.js +37 -2
- package/src/benchmark.js +299 -299
- package/src/codex/activity.js +324 -324
- package/src/codex/freshness.js +149 -142
- package/src/codex/techniques.js +4895 -4895
- package/src/context.js +326 -326
- package/src/continuous-ops.js +11 -1
- package/src/convert.js +340 -340
- package/src/copilot/config-parser.js +280 -280
- package/src/copilot/context.js +218 -218
- package/src/copilot/freshness.js +184 -177
- package/src/copilot/patch.js +238 -238
- package/src/copilot/techniques.js +3578 -3578
- package/src/cursor/freshness.js +194 -194
- package/src/cursor/patch.js +243 -243
- package/src/cursor/techniques.js +3735 -3735
- package/src/doctor.js +201 -201
- package/src/fix-engine.js +511 -8
- package/src/formatters/csv.js +86 -86
- package/src/formatters/junit.js +123 -123
- package/src/formatters/markdown.js +164 -164
- package/src/formatters/otel.js +151 -151
- package/src/freshness.js +163 -156
- package/src/gemini/activity.js +402 -402
- package/src/gemini/context.js +290 -290
- package/src/gemini/freshness.js +188 -188
- package/src/gemini/patch.js +229 -229
- package/src/gemini/techniques.js +3811 -3811
- package/src/governance.js +533 -533
- package/src/harmony/audit.js +306 -306
- package/src/i18n.js +63 -63
- package/src/insights.js +119 -119
- package/src/integrations.js +134 -134
- package/src/locales/en.json +33 -33
- package/src/locales/es.json +33 -33
- package/src/migrate.js +354 -354
- package/src/opencode/activity.js +286 -286
- package/src/opencode/freshness.js +137 -137
- package/src/opencode/techniques.js +3450 -3450
- package/src/safe-glyph.js +97 -0
- package/src/setup/analysis.js +12 -12
- package/src/setup.js +13 -6
- package/src/shallow-risk/index.js +113 -56
- package/src/shallow-risk/patterns/agent-config-cross-platform-drift.js +51 -50
- package/src/shallow-risk/patterns/agent-config-dangerous-autoapprove.js +47 -46
- package/src/shallow-risk/patterns/agent-config-deprecated-keys.js +47 -46
- package/src/shallow-risk/patterns/agent-config-framework-version-mismatch.js +138 -0
- package/src/shallow-risk/patterns/agent-config-missing-file.js +318 -317
- package/src/shallow-risk/patterns/agent-config-script-not-in-package-json.js +108 -0
- package/src/shallow-risk/patterns/agent-config-secret-literal.js +52 -49
- package/src/shallow-risk/patterns/agent-config-stack-contradiction.js +35 -34
- package/src/shallow-risk/patterns/hook-script-missing.js +71 -70
- package/src/shallow-risk/patterns/mcp-server-no-allowlist.js +53 -52
- package/src/shallow-risk/shared.js +653 -648
- package/src/source-urls.js +295 -295
- package/src/state-paths.js +85 -85
- package/src/supplemental-checks.js +805 -805
- package/src/telemetry.js +160 -160
- package/src/watch.js +46 -0
- package/src/windsurf/context.js +359 -359
- package/src/windsurf/freshness.js +194 -194
- package/src/windsurf/patch.js +231 -231
- package/src/windsurf/techniques.js +3779 -3779
package/package.json
CHANGED
|
@@ -1,67 +1,75 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@nerviq/cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "The intelligent nervous system for AI coding agents — 2,441 checks (8 platforms × ~300 governance rules), 10 languages, 62 domain packs. Audit, align, and amplify.",
|
|
5
|
-
"main": "src/index.js",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"test
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@nerviq/cli",
|
|
3
|
+
"version": "1.30.0",
|
|
4
|
+
"description": "The intelligent nervous system for AI coding agents — 2,441 checks (8 platforms × ~300 governance rules), 10 languages, 62 domain packs. Audit, align, and amplify.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.js",
|
|
8
|
+
"./sdk": "./sdk/index.js",
|
|
9
|
+
"./sdk/types": "./sdk/index.d.ts",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"nerviq": "bin/cli.js",
|
|
14
|
+
"@nerviq/cli": "bin/cli.js",
|
|
15
|
+
"nerviq-mcp": "src/mcp-server.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin",
|
|
19
|
+
"src",
|
|
20
|
+
"sdk",
|
|
21
|
+
"README.md",
|
|
22
|
+
"docs",
|
|
23
|
+
"contracts",
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"SECURITY.md"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"start": "node bin/cli.js",
|
|
29
|
+
"build": "npm pack --dry-run",
|
|
30
|
+
"test": "node test/run.js",
|
|
31
|
+
"verify:release-metadata": "node tools/validate-release-metadata.js",
|
|
32
|
+
"prepublish:check": "node tools/pre-publish.js",
|
|
33
|
+
"prepublishOnly": "node tools/pre-publish.js",
|
|
34
|
+
"postinstall": "node tools/postinstall.js || true",
|
|
35
|
+
"test:jest": "jest",
|
|
36
|
+
"test:coverage": "jest --coverage",
|
|
37
|
+
"test:all": "npm test && npx jest && node test/check-matrix.js && node test/codex-check-matrix.js && node test/gemini-check-matrix.js && node test/copilot-check-matrix.js && node test/cursor-check-matrix.js && node test/windsurf-check-matrix.js && node test/aider-check-matrix.js && node test/opencode-check-matrix.js && node test/golden-matrix.js && node test/codex-golden-matrix.js && node test/gemini-golden-matrix.js && node test/copilot-golden-matrix.js && node test/cursor-golden-matrix.js && node test/windsurf-golden-matrix.js && node test/aider-golden-matrix.js && node test/opencode-golden-matrix.js",
|
|
38
|
+
"benchmark:perf": "node tools/benchmark.js",
|
|
39
|
+
"announce:release": "node tools/announce-release.js",
|
|
40
|
+
"catalog": "node -e \"const {generateCatalog}=require('./src/catalog');console.log(JSON.stringify(generateCatalog(),null,2))\""
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"nerviq",
|
|
44
|
+
"ai-agents",
|
|
45
|
+
"agent-governance",
|
|
46
|
+
"agent-config",
|
|
47
|
+
"harmony",
|
|
48
|
+
"synergy",
|
|
49
|
+
"audit",
|
|
50
|
+
"claude",
|
|
51
|
+
"codex",
|
|
52
|
+
"gemini",
|
|
53
|
+
"copilot",
|
|
54
|
+
"cursor",
|
|
55
|
+
"windsurf",
|
|
56
|
+
"aider",
|
|
57
|
+
"developer-tools",
|
|
58
|
+
"cli",
|
|
59
|
+
"mcp",
|
|
60
|
+
"multi-agent"
|
|
61
|
+
],
|
|
62
|
+
"author": "Nerviq <hello@nerviq.net>",
|
|
63
|
+
"license": "AGPL-3.0",
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "git+https://github.com/nerviq/nerviq.git"
|
|
67
|
+
},
|
|
68
|
+
"homepage": "https://nerviq.net",
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=18.0.0"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"jest": "^30.3.0"
|
|
74
|
+
}
|
|
75
|
+
}
|
package/sdk/README.md
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Nerviq SDK (bundled inside `@nerviq/cli`)
|
|
2
2
|
|
|
3
3
|
Programmatic SDK for Nerviq audit, Harmony, catalog access, and experimental Synergy workflows.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @nerviq/
|
|
8
|
+
npm install @nerviq/cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
The SDK ships bundled inside `@nerviq/cli` per MEMO-03 (B = BUNDLE, signed 2026-04-28). There is no separate `@nerviq/sdk` npm package — earlier docs that referenced one were aspirational. Both import paths below work:
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
const sdk = require('@nerviq/cli/sdk'); // explicit SDK subpath (typed, input-validated)
|
|
15
|
+
const { audit } = require('@nerviq/cli'); // top-level — same exports
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The two paths return identical surface; pick whichever is cleaner for your codebase.
|
|
19
|
+
|
|
11
20
|
## Stability
|
|
12
21
|
|
|
13
22
|
- Stable for production workflows: `audit`, `harmonyAudit`, `detectPlatforms`, `getCatalog`
|
|
@@ -17,7 +26,7 @@ npm install @nerviq/sdk
|
|
|
17
26
|
## Quick Start
|
|
18
27
|
|
|
19
28
|
```js
|
|
20
|
-
const { audit, harmonyAudit, detectPlatforms } = require('@nerviq/sdk');
|
|
29
|
+
const { audit, harmonyAudit, detectPlatforms } = require('@nerviq/cli/sdk');
|
|
21
30
|
|
|
22
31
|
async function main() {
|
|
23
32
|
const repoDir = process.cwd();
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# LangChain integration — using Nerviq as a tool
|
|
2
|
+
|
|
3
|
+
> Reference example for AI-09. Wires `@nerviq/cli/sdk` into a LangChain
|
|
4
|
+
> agent as a callable tool, so an autonomous LangChain agent can audit
|
|
5
|
+
> its own repo, check Harmony Score, and surface stale references mid-task.
|
|
6
|
+
>
|
|
7
|
+
> Pairs with: [`self-governing-agent.js`](./self-governing-agent.js) +
|
|
8
|
+
> [/docs/for-agents](https://nerviq.net/docs/for-agents) on the site.
|
|
9
|
+
|
|
10
|
+
## Why an agent should call Nerviq
|
|
11
|
+
|
|
12
|
+
A LangChain agent operating inside a developer's repo benefits from knowing whether the agent-config files it's reading are coherent across platforms. Without that awareness, the agent can confidently follow instructions in `CLAUDE.md` that contradict instructions in `AGENTS.md` — and produce code that breaks in someone else's tooling.
|
|
13
|
+
|
|
14
|
+
Wiring Nerviq as a LangChain tool exposes three primitives:
|
|
15
|
+
|
|
16
|
+
- `nerviq_audit` — score the repo on a specific platform
|
|
17
|
+
- `nerviq_harmony` — measure cross-platform drift
|
|
18
|
+
- `nerviq_stale_references` — surface the headline stale-reference findings
|
|
19
|
+
|
|
20
|
+
## JavaScript / Node example
|
|
21
|
+
|
|
22
|
+
```js
|
|
23
|
+
const { audit, harmonyAudit } = require('@nerviq/cli/sdk');
|
|
24
|
+
const { DynamicTool } = require('@langchain/core/tools');
|
|
25
|
+
|
|
26
|
+
const nerviqAuditTool = new DynamicTool({
|
|
27
|
+
name: 'nerviq_audit',
|
|
28
|
+
description:
|
|
29
|
+
'Audit the AI coding agent configuration of the given repo directory. ' +
|
|
30
|
+
'Returns score (0-100), passed/failed counts, top stale-reference findings, ' +
|
|
31
|
+
'and topNextActions. Call this before substantive code changes when the ' +
|
|
32
|
+
'task touches CLAUDE.md, AGENTS.md, .cursor/rules, .mcp.json, or hooks.',
|
|
33
|
+
func: async (dir) => {
|
|
34
|
+
const result = await audit(dir || process.cwd(), 'claude');
|
|
35
|
+
return JSON.stringify({
|
|
36
|
+
score: result.score,
|
|
37
|
+
organicScore: result.organicScore,
|
|
38
|
+
passed: result.passed,
|
|
39
|
+
failed: result.failed,
|
|
40
|
+
staleReferences: result.staleReferences || null,
|
|
41
|
+
topNextActions: (result.liteSummary && result.liteSummary.topNextActions) || [],
|
|
42
|
+
}, null, 2);
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const nerviqHarmonyTool = new DynamicTool({
|
|
47
|
+
name: 'nerviq_harmony',
|
|
48
|
+
description:
|
|
49
|
+
'Measure cross-platform configuration drift between AI coding agents in ' +
|
|
50
|
+
'the given repo. Returns harmonyScore (0-100) plus a list of named ' +
|
|
51
|
+
'drifts. Only meaningful when 2+ platforms are detected.',
|
|
52
|
+
func: async (dir) => {
|
|
53
|
+
const result = await harmonyAudit(dir || process.cwd());
|
|
54
|
+
return JSON.stringify({
|
|
55
|
+
harmonyScore: result.harmonyScore,
|
|
56
|
+
activePlatforms: result.activePlatforms,
|
|
57
|
+
drifts: (result.drift && result.drift.drifts) || [],
|
|
58
|
+
}, null, 2);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Add to your agent's tool list:
|
|
63
|
+
const tools = [nerviqAuditTool, nerviqHarmonyTool /*, ...your other tools */];
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Python via subprocess
|
|
67
|
+
|
|
68
|
+
LangChain agents in Python can shell out to the CLI's `--agent-mode --json` surface:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from langchain_core.tools import tool
|
|
72
|
+
import json
|
|
73
|
+
import subprocess
|
|
74
|
+
|
|
75
|
+
@tool
|
|
76
|
+
def nerviq_audit(dir: str = ".") -> str:
|
|
77
|
+
"""Audit AI coding agent configuration. Returns score, stale references,
|
|
78
|
+
top next actions. Call before substantive code changes."""
|
|
79
|
+
result = subprocess.run(
|
|
80
|
+
["npx", "@nerviq/cli", "audit", "--json", "--agent-mode", "--dir", dir],
|
|
81
|
+
capture_output=True, text=True, timeout=60,
|
|
82
|
+
)
|
|
83
|
+
if result.returncode not in (0, 1, 2):
|
|
84
|
+
return json.dumps({"error": result.stderr})
|
|
85
|
+
return result.stdout # Already JSON
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## CrewAI / AutoGen / generic orchestrators
|
|
89
|
+
|
|
90
|
+
Same pattern: any orchestrator that supports tool definitions can wrap the SDK or shell out to `npx @nerviq/cli audit --json`. The JSON envelope is documented at [/docs/for-agents](https://nerviq.net/docs/for-agents) and stable per CTO-01..05 + BUG-01 (machine-output contract).
|
|
91
|
+
|
|
92
|
+
For CrewAI specifically:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from crewai.tools import tool
|
|
96
|
+
import subprocess
|
|
97
|
+
|
|
98
|
+
@tool("Nerviq audit tool")
|
|
99
|
+
def nerviq_audit(dir: str = "."):
|
|
100
|
+
"""Audit cross-platform AI coding agent configuration."""
|
|
101
|
+
out = subprocess.run(
|
|
102
|
+
["npx", "@nerviq/cli", "audit", "--json", "--dir", dir],
|
|
103
|
+
capture_output=True, text=True, timeout=60,
|
|
104
|
+
).stdout
|
|
105
|
+
return out
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Don't bypass user consent
|
|
109
|
+
|
|
110
|
+
Per the [/docs/for-agents](https://nerviq.net/docs/for-agents) trust-boundary policy: the agent should NOT silently apply `--apply --auto` on critical fixes that materially modify governance posture (deny rules, MCP permissions, hooks). Surface the plan via the audit/harmony tool, let the user approve, then apply. The CLI gates `--apply` on `--auto` for exactly this reason — single-flag bypass is intentionally blocked.
|
|
111
|
+
|
|
112
|
+
## When to call which tool
|
|
113
|
+
|
|
114
|
+
| Situation | Call |
|
|
115
|
+
|---|---|
|
|
116
|
+
| Task start | `nerviq_audit` (always) |
|
|
117
|
+
| Task touches multiple agents' config | `nerviq_harmony` (drift check) |
|
|
118
|
+
| Stale-reference count > 0 in audit result | Surface to user via the audit response, ask whether to proceed |
|
|
119
|
+
| Task complete | `nerviq_audit` again, compare scores, surface delta to user |
|
|
120
|
+
| User accepts a recommendation | (Optionally) record via `npx @nerviq/cli feedback --key <K> --status accepted` so the local learning loop benefits |
|
|
121
|
+
|
|
122
|
+
## Reference repo
|
|
123
|
+
|
|
124
|
+
The full self-governing loop reference (5-step pre/harmony/task/post/feedback pattern) lives at [`sdk/examples/self-governing-agent.js`](./self-governing-agent.js). Read that first if you're implementing the orchestration manually rather than letting LangChain/CrewAI drive the loop.
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
CC0 — copy, modify, integrate freely.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Reference: self-governing AI coding agent loop using @nerviq/cli/sdk.
|
|
4
|
+
*
|
|
5
|
+
* This file is the AI-07 reference example for the docs/for-agents page on
|
|
6
|
+
* nerviq.net. It implements the "Self-governing agent pattern" documented
|
|
7
|
+
* there: an agent that audits the repo before acting, runs targeted fixes,
|
|
8
|
+
* makes the actual code change, re-audits to detect regression, and records
|
|
9
|
+
* outcomes back into the local learning loop.
|
|
10
|
+
*
|
|
11
|
+
* Usage (after `npm install @nerviq/cli`):
|
|
12
|
+
* node node_modules/@nerviq/cli/sdk/examples/self-governing-agent.js [repo-dir]
|
|
13
|
+
*
|
|
14
|
+
* Default repo-dir is process.cwd().
|
|
15
|
+
*
|
|
16
|
+
* NOT autonomous in the dangerous sense:
|
|
17
|
+
* - This loop NEVER calls --apply --auto silently. Mutations of governance
|
|
18
|
+
* posture (deny rules, MCP permissions, hooks) require explicit user
|
|
19
|
+
* consent. The example surfaces a plan and waits for the next human
|
|
20
|
+
* decision; that's by design and matches the docs/for-agents constraint.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
// Resolve the SDK from npm install OR from the in-repo path (for running this
|
|
26
|
+
// example directly from a checkout of nerviq/nerviq).
|
|
27
|
+
function loadSdk() {
|
|
28
|
+
try {
|
|
29
|
+
return require('@nerviq/cli/sdk');
|
|
30
|
+
} catch {
|
|
31
|
+
return require('../index.js');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const { audit, harmonyAudit, detectPlatforms } = loadSdk();
|
|
35
|
+
const path = require('path');
|
|
36
|
+
|
|
37
|
+
async function selfGoverningLoop(repoDir, opts = {}) {
|
|
38
|
+
const dir = path.resolve(repoDir);
|
|
39
|
+
const log = opts.log || console.log;
|
|
40
|
+
const HARMONY_DRIFT_THRESHOLD = 60;
|
|
41
|
+
|
|
42
|
+
// ── Step 1. Pre-task audit ──────────────────────────────────────────────
|
|
43
|
+
log('[1/5] pre-task audit…');
|
|
44
|
+
const platforms = detectPlatforms(dir);
|
|
45
|
+
log(` platforms: ${platforms.join(', ') || '(none detected — single-platform repo)'}`);
|
|
46
|
+
|
|
47
|
+
const pre = await audit(dir, opts.platform || (platforms[0] || 'claude'));
|
|
48
|
+
log(` score: ${pre.score}/100 organic: ${pre.organicScore}/100 failed: ${pre.failed}`);
|
|
49
|
+
|
|
50
|
+
// The headline value: stale references. Surface BEFORE doing anything.
|
|
51
|
+
if (pre.staleReferences && pre.staleReferences.count > 0) {
|
|
52
|
+
log(` 📌 ${pre.staleReferences.headline}`);
|
|
53
|
+
for (const sample of pre.staleReferences.topSample) {
|
|
54
|
+
log(` · ${sample.file}:${sample.line} — ${sample.fix.split('\n')[0].slice(0, 100)}`);
|
|
55
|
+
}
|
|
56
|
+
log(' ↳ recommend: surface to user, ask whether to proceed despite stale refs');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Step 2. Cross-platform Harmony check (only if multi-platform) ──────
|
|
60
|
+
if (platforms.length >= 2) {
|
|
61
|
+
log('[2/5] harmony check (cross-platform drift)…');
|
|
62
|
+
const harmony = await harmonyAudit(dir);
|
|
63
|
+
log(` harmonyScore: ${harmony.harmonyScore}/100`);
|
|
64
|
+
|
|
65
|
+
if (harmony.harmonyScore < HARMONY_DRIFT_THRESHOLD) {
|
|
66
|
+
log(` ⚠️ harmony below ${HARMONY_DRIFT_THRESHOLD} — drift between platforms is forming`);
|
|
67
|
+
log(' ↳ recommend: surface drifts to user, decide whether to harmony-sync before proceeding');
|
|
68
|
+
// NOTE: a real agent would surface harmony.drift list here and wait for
|
|
69
|
+
// user approval before running `nerviq harmony-sync --fix`. We do NOT
|
|
70
|
+
// call it silently from the agent loop.
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
log('[2/5] harmony check skipped (single-platform repo)');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Step 3. Make the actual code change ─────────────────────────────────
|
|
77
|
+
log('[3/5] doing the actual task… (placeholder — replace with the real agent action)');
|
|
78
|
+
// In a real agent: execute the user's task. Edit files, run tests, etc.
|
|
79
|
+
// For this example, simulate a small change so step 4's diff-only audit has
|
|
80
|
+
// something to compare against.
|
|
81
|
+
if (typeof opts.doWork === 'function') {
|
|
82
|
+
await opts.doWork({ dir, preAuditScore: pre.score });
|
|
83
|
+
} else {
|
|
84
|
+
log(' (no doWork callback provided — skipping)');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Step 4. Post-task diff-only re-audit ────────────────────────────────
|
|
88
|
+
log('[4/5] post-task diff-only audit…');
|
|
89
|
+
const post = await audit(dir, opts.platform || (platforms[0] || 'claude'));
|
|
90
|
+
const delta = post.score - pre.score;
|
|
91
|
+
const arrow = delta > 0 ? `+${delta}` : delta < 0 ? `${delta}` : '0';
|
|
92
|
+
log(` score: ${post.score}/100 Δ ${arrow} failed: ${post.failed}`);
|
|
93
|
+
|
|
94
|
+
if (delta < -3) {
|
|
95
|
+
log(' 🔴 score dropped materially — recommend: rollback or human review');
|
|
96
|
+
} else if (delta > 0) {
|
|
97
|
+
log(' ✓ score improved');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Step 5. Record outcome (learning loop) ─────────────────────────────
|
|
101
|
+
log('[5/5] outcome recording — call `nerviq feedback --key <K> --status accepted|rejected|deferred --score-delta <delta>`');
|
|
102
|
+
log(' (the agent should invoke this once per recommendation it acted on, so suggest-rules can learn the team\'s actual preferences)');
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
pre,
|
|
106
|
+
post,
|
|
107
|
+
delta,
|
|
108
|
+
platforms,
|
|
109
|
+
recommendation:
|
|
110
|
+
delta < -3
|
|
111
|
+
? 'rollback-or-review'
|
|
112
|
+
: pre.staleReferences && pre.staleReferences.count > 0
|
|
113
|
+
? 'surface-stale-references-to-user'
|
|
114
|
+
: delta > 0
|
|
115
|
+
? 'continue'
|
|
116
|
+
: 'no-change',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (require.main === module) {
|
|
121
|
+
const dir = process.argv[2] || process.cwd();
|
|
122
|
+
selfGoverningLoop(dir, { log: console.log })
|
|
123
|
+
.then((result) => {
|
|
124
|
+
console.log('\n=== Loop complete ===');
|
|
125
|
+
console.log(`Recommendation: ${result.recommendation}`);
|
|
126
|
+
console.log(`Pre/post score: ${result.pre.score} → ${result.post.score} (Δ ${result.delta})`);
|
|
127
|
+
process.exitCode = result.recommendation === 'rollback-or-review' ? 1 : 0;
|
|
128
|
+
})
|
|
129
|
+
.catch((err) => {
|
|
130
|
+
console.error(`error: ${err.message}`);
|
|
131
|
+
process.exitCode = 1;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = { selfGoverningLoop };
|
package/sdk/index.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export type NerviqPlatform =
|
|
2
|
+
| 'claude'
|
|
3
|
+
| 'codex'
|
|
4
|
+
| 'gemini'
|
|
5
|
+
| 'copilot'
|
|
6
|
+
| 'cursor'
|
|
7
|
+
| 'windsurf'
|
|
8
|
+
| 'aider'
|
|
9
|
+
| 'opencode';
|
|
10
|
+
|
|
11
|
+
export interface AuditFinding {
|
|
12
|
+
key: string;
|
|
13
|
+
id?: string | null;
|
|
14
|
+
name: string;
|
|
15
|
+
category?: string | null;
|
|
16
|
+
impact?: 'critical' | 'high' | 'medium' | 'low' | null;
|
|
17
|
+
fix?: string | null;
|
|
18
|
+
passed: boolean | null;
|
|
19
|
+
file?: string | null;
|
|
20
|
+
line?: number | null;
|
|
21
|
+
sourceUrl?: string | null;
|
|
22
|
+
confidence?: number | string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AuditAction {
|
|
26
|
+
key: string;
|
|
27
|
+
id?: string | null;
|
|
28
|
+
name: string;
|
|
29
|
+
impact?: 'critical' | 'high' | 'medium' | 'low' | null;
|
|
30
|
+
category?: string | null;
|
|
31
|
+
fix?: string | null;
|
|
32
|
+
why?: string | null;
|
|
33
|
+
sourceUrl?: string | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AuditResult {
|
|
37
|
+
platform: string;
|
|
38
|
+
platformLabel: string;
|
|
39
|
+
score: number;
|
|
40
|
+
passed: number;
|
|
41
|
+
failed: number;
|
|
42
|
+
skipped: number;
|
|
43
|
+
checkCount: number;
|
|
44
|
+
results: AuditFinding[];
|
|
45
|
+
quickWins: AuditAction[];
|
|
46
|
+
topNextActions: AuditAction[];
|
|
47
|
+
suggestedNextCommand?: string;
|
|
48
|
+
/** Convenience alias for `passed` */
|
|
49
|
+
passing: number;
|
|
50
|
+
/** Convenience alias for `passed + failed` */
|
|
51
|
+
total: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface HarmonyResult {
|
|
55
|
+
harmonyScore: number;
|
|
56
|
+
platformScores: Record<string, number | null>;
|
|
57
|
+
platformResults: Record<string, AuditResult | null>;
|
|
58
|
+
drift: {
|
|
59
|
+
drifts: Array<Record<string, unknown>>;
|
|
60
|
+
harmonyScore: number;
|
|
61
|
+
};
|
|
62
|
+
recommendations: Array<Record<string, unknown>>;
|
|
63
|
+
activePlatforms: Array<Record<string, unknown>>;
|
|
64
|
+
model: Record<string, unknown>;
|
|
65
|
+
/** Convenience alias for `harmonyScore` */
|
|
66
|
+
average: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface Check {
|
|
70
|
+
platform: string;
|
|
71
|
+
id: string | null;
|
|
72
|
+
key: string;
|
|
73
|
+
name: string | null;
|
|
74
|
+
category: string | null;
|
|
75
|
+
impact: string | null;
|
|
76
|
+
rating: string | null;
|
|
77
|
+
fix: string | null;
|
|
78
|
+
sourceUrl: string | null;
|
|
79
|
+
confidence: number | null;
|
|
80
|
+
lastVerified?: string | null;
|
|
81
|
+
template?: string | null;
|
|
82
|
+
deprecated?: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface RoutingChoice {
|
|
86
|
+
platform: string;
|
|
87
|
+
confidence: number;
|
|
88
|
+
reasoning: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface RoutingResult {
|
|
92
|
+
recommended: RoutingChoice | null;
|
|
93
|
+
alternatives: RoutingChoice[];
|
|
94
|
+
taskType: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface SynergyResult {
|
|
98
|
+
dir: string;
|
|
99
|
+
activePlatforms: string[];
|
|
100
|
+
platformAudits: Record<string, AuditResult>;
|
|
101
|
+
compound: Record<string, unknown>;
|
|
102
|
+
amplification: number;
|
|
103
|
+
compensation: Record<string, unknown>;
|
|
104
|
+
patterns: Array<Record<string, unknown>>;
|
|
105
|
+
recommendations: Array<Record<string, unknown>>;
|
|
106
|
+
errors: Array<{ platform: string; message: string }>;
|
|
107
|
+
report: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export declare function audit(dir: string, platform?: NerviqPlatform): Promise<AuditResult>;
|
|
111
|
+
export declare function harmonyAudit(dir: string): Promise<HarmonyResult>;
|
|
112
|
+
export declare function synergyReport(dir: string): Promise<SynergyResult>;
|
|
113
|
+
export declare function detectPlatforms(dir: string): string[];
|
|
114
|
+
export declare function getCatalog(): Check[];
|
|
115
|
+
export declare function routeTask(description: string, platforms?: string[]): RoutingResult;
|
package/sdk/index.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
const VALID_PLATFORMS = ['claude', 'codex', 'cursor', 'copilot', 'gemini', 'windsurf', 'aider', 'opencode'];
|
|
5
|
+
|
|
6
|
+
function loadCore() {
|
|
7
|
+
try {
|
|
8
|
+
return require('@nerviq/cli');
|
|
9
|
+
} catch {
|
|
10
|
+
return require('..');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function validateDir(dir) {
|
|
15
|
+
if (!dir || typeof dir !== 'string') {
|
|
16
|
+
throw new Error('dir is required and must be a string. Pass a valid directory path.');
|
|
17
|
+
}
|
|
18
|
+
const resolved = path.resolve(dir);
|
|
19
|
+
if (!fs.existsSync(resolved)) {
|
|
20
|
+
throw new Error(`Directory not found: ${resolved}. Pass an existing directory path.`);
|
|
21
|
+
}
|
|
22
|
+
return resolved;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function validatePlatform(platform) {
|
|
26
|
+
if (platform && !VALID_PLATFORMS.includes(platform)) {
|
|
27
|
+
throw new Error(`Unsupported platform '${platform}'. Use one of: ${VALID_PLATFORMS.join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function audit(dir, platform = 'claude') {
|
|
32
|
+
const resolved = validateDir(dir);
|
|
33
|
+
validatePlatform(platform);
|
|
34
|
+
const core = loadCore();
|
|
35
|
+
const result = await core.audit({
|
|
36
|
+
dir: resolved,
|
|
37
|
+
platform,
|
|
38
|
+
silent: true,
|
|
39
|
+
});
|
|
40
|
+
// Add convenience aliases for SDK consumers
|
|
41
|
+
if (result) {
|
|
42
|
+
result.passing = result.passed;
|
|
43
|
+
result.total = (result.passed || 0) + (result.failed || 0);
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function harmonyAudit(dir) {
|
|
49
|
+
const resolved = validateDir(dir);
|
|
50
|
+
const core = loadCore();
|
|
51
|
+
const result = await core.harmonyAudit({
|
|
52
|
+
dir: resolved,
|
|
53
|
+
silent: true,
|
|
54
|
+
});
|
|
55
|
+
// Add convenience alias for SDK consumers
|
|
56
|
+
if (result) {
|
|
57
|
+
result.average = result.harmonyScore;
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function synergyReport(dir) {
|
|
63
|
+
const resolved = validateDir(dir);
|
|
64
|
+
const core = loadCore();
|
|
65
|
+
return core.synergyReport(resolved);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function detectPlatforms(dir) {
|
|
69
|
+
const resolved = validateDir(dir);
|
|
70
|
+
const core = loadCore();
|
|
71
|
+
return core.detectPlatforms(resolved);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getCatalog() {
|
|
75
|
+
const core = loadCore();
|
|
76
|
+
return core.getCatalog();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function routeTask(description, platforms) {
|
|
80
|
+
if (!description || typeof description !== 'string') {
|
|
81
|
+
throw new Error('description is required and must be a non-empty string.');
|
|
82
|
+
}
|
|
83
|
+
const core = loadCore();
|
|
84
|
+
return core.routeTask(description, platforms || []);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
audit,
|
|
89
|
+
harmonyAudit,
|
|
90
|
+
synergyReport,
|
|
91
|
+
detectPlatforms,
|
|
92
|
+
getCatalog,
|
|
93
|
+
routeTask,
|
|
94
|
+
};
|
package/sdk/package.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nerviq/sdk",
|
|
3
|
+
"version": "0.9.5",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"types": "index.d.ts",
|
|
6
|
+
"description": "Programmatic SDK for Nerviq — audit, harmony, synergy for AI coding agents",
|
|
7
|
+
"license": "AGPL-3.0",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@nerviq/cli": "^0.9.5"
|
|
10
|
+
}
|
|
11
|
+
}
|