nativesoul 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.
- package/CHANGELOG.md +43 -0
- package/LICENSE +19 -0
- package/README.md +384 -0
- package/dist/packages/cli/src/index.js +1066 -0
- package/dist/packages/core/src/secret-patterns.json +25 -0
- package/dist/packages/mcp-server/src/index.js +782 -0
- package/docs/adr/ADR-001-node-first-runtime.md +20 -0
- package/docs/adr/ADR-002-workspace-and-state.md +16 -0
- package/docs/adr/ADR-003-schemas-and-adapter-sdk.md +15 -0
- package/docs/adr/ADR-004-policy-leases-and-heartbeat.md +15 -0
- package/docs/adr/ADR-005-privacy-export-delete-and-supply-chain.md +16 -0
- package/docs/adr/ADR-006-adapter-preview-before-install.md +21 -0
- package/docs/adr/ADR-007-managed-block-backup-and-rollback.md +16 -0
- package/docs/adr/ADR-008-mcp-registration-manifest-before-write.md +16 -0
- package/docs/adr/ADR-009-local-capability-registry.md +16 -0
- package/docs/adr/ADR-010-mcp-capability-tools-read-only.md +15 -0
- package/docs/adr/ADR-011-local-audit-cli-export.md +16 -0
- package/docs/adr/ADR-012-npm-package-runtime-scope.md +16 -0
- package/docs/adr/ADR-013-core-domain-ledger-v1.md +16 -0
- package/docs/adr/ADR-014-memory-lifecycle-v1.md +16 -0
- package/docs/adr/ADR-015-policy-standing-orders-v1.md +16 -0
- package/docs/adr/ADR-016-heartbeat-lease-idempotency-v1.md +16 -0
- package/docs/adr/ADR-017-reversible-mcp-registration-install.md +17 -0
- package/docs/adr/ADR-018-schedule-reconcile-guardrails-v1.md +17 -0
- package/docs/adr/ADR-019-commitments-lifecycle-v1.md +17 -0
- package/docs/adr/ADR-020-mcp-memory-lifecycle-tools.md +16 -0
- package/docs/adr/ADR-021-unified-install-orchestrator-v1.md +17 -0
- package/docs/adr/ADR-022-unified-uninstall-orchestrator-v1.md +20 -0
- package/docs/adr/ADR-023-deep-doctor-install-readiness-v1.md +18 -0
- package/docs/adr/ADR-024-capability-learn-cards-v1.md +18 -0
- package/docs/adr/ADR-025-bootstrap-capability-card-recall-v1.md +18 -0
- package/docs/adr/ADR-026-managed-block-bootstrap-handshake-v1.md +18 -0
- package/docs/adr/ADR-027-local-release-manifest-checksum-v1.md +17 -0
- package/docs/adr/ADR-028-openclaw-workspace-templates-v1.md +47 -0
- package/docs/adr/ADR-029-host-guided-onboarding-v1.md +98 -0
- package/docs/adr/ADR-030-host-surface-matrix-v1.md +93 -0
- package/docs/adr/ADR-031-host-parity-matrix-v2.md +91 -0
- package/docs/adr/ADR-032-nativesoul-hard-cut-rebrand.md +54 -0
- package/docs/adr/ADR-033-local-hybrid-memory-reflection.md +49 -0
- package/docs/adr/ADR-034-memory-importers-provenance.md +38 -0
- package/docs/adr/ADR-035-policy-pack-apply-rollback.md +34 -0
- package/docs/adr/ADR-036-local-operator-dashboard-readonly.md +38 -0
- package/docs/adr/ADR-037-full-native-evidence-contract.md +45 -0
- package/docs/adr/ADR-038-identity-vs-activation-onboarding.md +39 -0
- package/docs/adr/ADR-039-entitlements-offline-licensing.md +47 -0
- package/docs/adr/ADR-040-proprietary-free-to-install-distribution.md +52 -0
- package/docs/adr/ADR-041-project-identity-and-lightweight-registry.md +35 -0
- package/docs/adr/ADR-042-control-plane-direction-and-deferral.md +80 -0
- package/docs/adr/ADR-043-versioning-and-release-policy.md +60 -0
- package/docs/adr/ADR-044-pro-awareness-and-upgrade-nudge-policy.md +53 -0
- package/docs/adr/ADR-045-paid-only-closed-distribution.md +55 -0
- package/docs/legal/EULA.md +70 -0
- package/llm-install.txt +222 -0
- package/package.json +61 -0
- package/plugins/antigravity-nativesoul/GEMINI.md +17 -0
- package/plugins/antigravity-nativesoul/commands/nativesoul-explore.md +22 -0
- package/plugins/antigravity-nativesoul/commands/nativesoul-heartbeat.md +9 -0
- package/plugins/antigravity-nativesoul/commands/nativesoul-plan.md +23 -0
- package/plugins/antigravity-nativesoul/commands/nativesoul-recall.md +16 -0
- package/plugins/antigravity-nativesoul/commands/nativesoul-start.md +9 -0
- package/plugins/antigravity-nativesoul/hooks.json +59 -0
- package/plugins/antigravity-nativesoul/mcp_config.json +8 -0
- package/plugins/antigravity-nativesoul/nativesoul-package.json +22 -0
- package/plugins/antigravity-nativesoul/plugin.json +5 -0
- package/plugins/claude-nativesoul/.claude-plugin/plugin.json +90 -0
- package/plugins/claude-nativesoul/hooks/loader.cjs +23 -0
- package/plugins/claude-nativesoul/hooks/memory-flush.cjs +45 -0
- package/plugins/claude-nativesoul/hooks/pre-tool-use.cjs +53 -0
- package/plugins/claude-nativesoul/hooks/session-start.cjs +108 -0
- package/plugins/claude-nativesoul/hooks/user-prompt-submit.cjs +62 -0
- package/plugins/claude-nativesoul/skills/life-explore/SKILL.md +19 -0
- package/plugins/claude-nativesoul/skills/life-heartbeat/SKILL.md +16 -0
- package/plugins/claude-nativesoul/skills/life-plan/SKILL.md +23 -0
- package/plugins/claude-nativesoul/skills/life-recall/SKILL.md +18 -0
- package/plugins/claude-nativesoul/skills/life-start/SKILL.md +13 -0
- package/plugins/codex-nativesoul/.codex-plugin/plugin.json +83 -0
- package/plugins/codex-nativesoul/.mcp.json +8 -0
- package/plugins/codex-nativesoul/hooks/loader.cjs +23 -0
- package/plugins/codex-nativesoul/hooks/memory-flush.cjs +45 -0
- package/plugins/codex-nativesoul/hooks/pre-tool-use.cjs +53 -0
- package/plugins/codex-nativesoul/hooks/session-start.cjs +119 -0
- package/plugins/codex-nativesoul/hooks/user-prompt-submit.cjs +62 -0
- package/plugins/codex-nativesoul/skills/life-explore/SKILL.md +24 -0
- package/plugins/codex-nativesoul/skills/life-heartbeat/SKILL.md +21 -0
- package/plugins/codex-nativesoul/skills/life-plan/SKILL.md +28 -0
- package/plugins/codex-nativesoul/skills/life-recall/SKILL.md +23 -0
- package/plugins/codex-nativesoul/skills/life-start/SKILL.md +18 -0
- package/plugins/gemini-nativesoul/GEMINI.md +17 -0
- package/plugins/gemini-nativesoul/commands/nativesoul-explore.toml +3 -0
- package/plugins/gemini-nativesoul/commands/nativesoul-heartbeat.toml +2 -0
- package/plugins/gemini-nativesoul/commands/nativesoul-plan.toml +3 -0
- package/plugins/gemini-nativesoul/commands/nativesoul-recall.toml +3 -0
- package/plugins/gemini-nativesoul/commands/nativesoul-start.toml +2 -0
- package/plugins/gemini-nativesoul/gemini-extension.json +16 -0
- package/plugins/gemini-nativesoul/hooks/hooks.json +59 -0
- package/plugins/grok-nativesoul/.mcp.json +8 -0
- package/plugins/grok-nativesoul/AGENTS.md +17 -0
- package/plugins/grok-nativesoul/commands/nativesoul-explore.md +22 -0
- package/plugins/grok-nativesoul/commands/nativesoul-heartbeat.md +9 -0
- package/plugins/grok-nativesoul/commands/nativesoul-plan.md +23 -0
- package/plugins/grok-nativesoul/commands/nativesoul-recall.md +16 -0
- package/plugins/grok-nativesoul/commands/nativesoul-start.md +9 -0
- package/plugins/grok-nativesoul/hooks/hooks.json +59 -0
- package/plugins/grok-nativesoul/nativesoul-package.json +22 -0
- package/plugins/grok-nativesoul/plugin.json +5 -0
- package/plugins/shared/hooks/host-hook.cjs +207 -0
- package/plugins/shared/hooks/lib.cjs +279 -0
- package/plugins/shared/hooks/policy-fail-closed.cjs +95 -0
- package/policy-packs/local-readonly.json +47 -0
- package/policy-packs/team-review.json +49 -0
- package/schemas/audit-event.schema.json +18 -0
- package/schemas/capability-card.schema.json +37 -0
- package/schemas/capability.schema.json +25 -0
- package/schemas/commitment.schema.json +19 -0
- package/schemas/full-native-evidence.schema.json +82 -0
- package/schemas/lease.schema.json +16 -0
- package/schemas/memory.schema.json +31 -0
- package/schemas/migration.schema.json +14 -0
- package/schemas/policy-pack.schema.json +96 -0
- package/schemas/schedule.schema.json +16 -0
- package/schemas/session.schema.json +16 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# nativesoul-explore
|
|
2
|
+
|
|
3
|
+
Explore the current project with NativeSoul context.
|
|
4
|
+
|
|
5
|
+
Default policy: read-only and bounded. Do not edit files, install packages, start services, create schedulers, or perform external writes.
|
|
6
|
+
|
|
7
|
+
1. Call MCP `life_bootstrap` with `host="grok"`.
|
|
8
|
+
2. Inspect readiness with:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
nativesoul dashboard data --host grok --limit 25 --json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
3. Scope discovery to the operator request. Prefer targeted local reads:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
rg --files
|
|
18
|
+
rg "<specific term from the task>"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
4. Avoid broad unbounded scans. If a write is needed, stop and ask for normal approval.
|
|
22
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# nativesoul-heartbeat
|
|
2
|
+
|
|
3
|
+
Run a heartbeat tick without creating a hidden scheduler:
|
|
4
|
+
|
|
5
|
+
1. Claim a lease with host `grok`.
|
|
6
|
+
2. If the result is `HEARTBEAT_OK`, stay silent.
|
|
7
|
+
3. Complete only leases won by Grok.
|
|
8
|
+
|
|
9
|
+
Native schedule status remains `native_schedule_supported=false` until Grok documents an official scheduler surface.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# nativesoul-plan
|
|
2
|
+
|
|
3
|
+
Build a NativeSoul-aware plan for the current Grok task.
|
|
4
|
+
|
|
5
|
+
Default policy: read-only.
|
|
6
|
+
|
|
7
|
+
1. Call MCP `life_bootstrap` with `host="grok"` and the current working directory.
|
|
8
|
+
2. Read dashboard state:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
nativesoul dashboard data --host grok --limit 25 --json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
3. Read commitments and capabilities when relevant:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
nativesoul commitments list --project <project-id> --status open --limit 25 --json
|
|
18
|
+
nativesoul capabilities list --host grok --limit 25 --json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
4. Separate implemented, degraded/fallback, and approval-required work.
|
|
22
|
+
5. Do not mutate schedules, memory, policy, or files without explicit approval.
|
|
23
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# nativesoul-recall
|
|
2
|
+
|
|
3
|
+
Recall NativeSoul project memory and capability context for the current Grok task.
|
|
4
|
+
|
|
5
|
+
Default policy: read-only.
|
|
6
|
+
|
|
7
|
+
1. Prefer MCP `life_recall` with `host="grok"` and the current working directory.
|
|
8
|
+
2. If MCP is unavailable, run:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
nativesoul memory recall-hook --host grok --cwd "$PWD" --prompt "<task query>" --limit 8 --json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
3. Treat recovered memory as advisory and verify against files.
|
|
15
|
+
4. Report readiness honestly as `ready`, `full native capability`, `official-fallback`, or `degraded`.
|
|
16
|
+
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "node \"$GROK_PLUGIN_ROOT/shared/hooks/host-hook.cjs\" session-start --host grok --project-env GROK_WORKSPACE_ROOT",
|
|
9
|
+
"timeout": 9
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"UserPromptSubmit": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "node \"$GROK_PLUGIN_ROOT/shared/hooks/host-hook.cjs\" prompt-bootstrap --host grok --project-env GROK_WORKSPACE_ROOT",
|
|
20
|
+
"timeout": 9
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"PreToolUse": [
|
|
26
|
+
{
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "node \"$GROK_PLUGIN_ROOT/shared/hooks/host-hook.cjs\" pre-tool-use --host grok --project-env GROK_WORKSPACE_ROOT",
|
|
31
|
+
"timeout": 9
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"Stop": [
|
|
37
|
+
{
|
|
38
|
+
"hooks": [
|
|
39
|
+
{
|
|
40
|
+
"type": "command",
|
|
41
|
+
"command": "node \"$GROK_PLUGIN_ROOT/shared/hooks/host-hook.cjs\" memory-flush --host grok --project-env GROK_WORKSPACE_ROOT",
|
|
42
|
+
"timeout": 9
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"PreCompact": [
|
|
48
|
+
{
|
|
49
|
+
"hooks": [
|
|
50
|
+
{
|
|
51
|
+
"type": "command",
|
|
52
|
+
"command": "node \"$GROK_PLUGIN_ROOT/shared/hooks/host-hook.cjs\" memory-flush --host grok --project-env GROK_WORKSPACE_ROOT",
|
|
53
|
+
"timeout": 9
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "grok-nativesoul",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"nativeSoul": {
|
|
5
|
+
"host": "grok",
|
|
6
|
+
"packageKind": "native-plugin",
|
|
7
|
+
"nativeHooksSupported": true,
|
|
8
|
+
"nativeSchedulerSupported": false,
|
|
9
|
+
"mcpServer": {
|
|
10
|
+
"name": "nativesoul",
|
|
11
|
+
"command": "nativesoul-mcp",
|
|
12
|
+
"args": []
|
|
13
|
+
},
|
|
14
|
+
"commands": [
|
|
15
|
+
"nativesoul-start",
|
|
16
|
+
"nativesoul-heartbeat",
|
|
17
|
+
"nativesoul-recall",
|
|
18
|
+
"nativesoul-plan",
|
|
19
|
+
"nativesoul-explore"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const lib = require('./lib.cjs');
|
|
4
|
+
const policy = require('./policy-fail-closed.cjs');
|
|
5
|
+
|
|
6
|
+
const kind = (process.argv[2] || 'session-start').trim();
|
|
7
|
+
const options = parseOptions(process.argv.slice(3));
|
|
8
|
+
const host = (options.host || process.env.NATIVESOUL_HOOK_HOST || 'codex').trim();
|
|
9
|
+
const projectEnv = (options.projectEnv || process.env.NATIVESOUL_PROJECT_ENV || defaultProjectEnv(host)).trim();
|
|
10
|
+
const eventName = (options.event || process.env.NATIVESOUL_HOOK_EVENT_NAME || '').trim();
|
|
11
|
+
|
|
12
|
+
function parseOptions(args) {
|
|
13
|
+
const parsed = {};
|
|
14
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
15
|
+
const arg = args[index];
|
|
16
|
+
if (arg === '--host' && args[index + 1]) {
|
|
17
|
+
parsed.host = args[index + 1];
|
|
18
|
+
index += 1;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (arg === '--project-env' && args[index + 1]) {
|
|
22
|
+
parsed.projectEnv = args[index + 1];
|
|
23
|
+
index += 1;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (arg === '--event' && args[index + 1]) {
|
|
27
|
+
parsed.event = args[index + 1];
|
|
28
|
+
index += 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function defaultProjectEnv(currentHost) {
|
|
35
|
+
if (currentHost === 'codex') {
|
|
36
|
+
return 'CODEX_PROJECT_DIR';
|
|
37
|
+
}
|
|
38
|
+
if (currentHost === 'grok') {
|
|
39
|
+
return 'GROK_WORKSPACE_ROOT';
|
|
40
|
+
}
|
|
41
|
+
if (currentHost === 'gemini' || currentHost === 'antigravity') {
|
|
42
|
+
return 'GEMINI_CWD';
|
|
43
|
+
}
|
|
44
|
+
return 'CLAUDE_PROJECT_DIR';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function jsonOutput(value) {
|
|
48
|
+
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function hookEventName(input, fallback) {
|
|
52
|
+
return eventName || input.hook_event_name || input.hookEventName || input.event_name || input.eventName || process.env.GROK_HOOK_EVENT || fallback;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function bootstrapContext(cwd) {
|
|
56
|
+
const injectArgs = lib.appendHomeArg(['bootstrap', '--host', host, '--cwd', cwd, '--format', 'inject', '--json']);
|
|
57
|
+
const inject = lib.spawnNativeSoul(injectArgs);
|
|
58
|
+
const injectJson = lib.parseJsonStdout(inject);
|
|
59
|
+
if (injectJson && typeof injectJson.content === 'string') {
|
|
60
|
+
return {
|
|
61
|
+
content: injectJson.content,
|
|
62
|
+
degraded: false,
|
|
63
|
+
source: 'inject',
|
|
64
|
+
warnings: Array.isArray(injectJson.warnings) ? injectJson.warnings : []
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const cacheArgs = lib.appendHomeArg(['bootstrap', '--host', host, '--cwd', cwd, '--format', 'cache', '--json']);
|
|
69
|
+
const cache = lib.spawnNativeSoul(cacheArgs);
|
|
70
|
+
const cacheJson = lib.parseJsonStdout(cache);
|
|
71
|
+
if (cacheJson && typeof cacheJson.content === 'string') {
|
|
72
|
+
return {
|
|
73
|
+
content: cacheJson.content,
|
|
74
|
+
degraded: true,
|
|
75
|
+
source: 'cache',
|
|
76
|
+
warnings: Array.isArray(cacheJson.warnings) ? cacheJson.warnings : []
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function outputBootstrapContext(input, fallbackEvent) {
|
|
84
|
+
const cwd = lib.resolveProjectDir(input, projectEnv);
|
|
85
|
+
const context = bootstrapContext(cwd);
|
|
86
|
+
if (!context || context.content.trim().length === 0) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const hookName = hookEventName(input, fallbackEvent);
|
|
90
|
+
jsonOutput({
|
|
91
|
+
context: context.content,
|
|
92
|
+
degraded: context.degraded,
|
|
93
|
+
source: context.source,
|
|
94
|
+
warnings: context.warnings,
|
|
95
|
+
hookSpecificOutput: {
|
|
96
|
+
hookEventName: hookName,
|
|
97
|
+
additionalContext: context.content
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function runRecallHook(input) {
|
|
103
|
+
const prompt = input.prompt || input.userPrompt || input.message || '';
|
|
104
|
+
if (typeof prompt !== 'string' || prompt.trim().length === 0) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const cwd = lib.resolveProjectDir(input, projectEnv);
|
|
108
|
+
const args = lib.appendHomeArg(['memory', 'recall-hook', '--host', host, '--cwd', cwd, '--json']);
|
|
109
|
+
const result = lib.spawnNativeSoul(args, { input: `${JSON.stringify(input)}\n` });
|
|
110
|
+
const json = lib.parseJsonStdout(result);
|
|
111
|
+
if (!json || typeof json.context !== 'string' || json.context.trim().length === 0) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return json.context.trim();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function recallContext(input) {
|
|
118
|
+
const recall = runRecallHook(input);
|
|
119
|
+
if (!recall) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
jsonOutput({
|
|
123
|
+
hookSpecificOutput: {
|
|
124
|
+
hookEventName: hookEventName(input, 'UserPromptSubmit'),
|
|
125
|
+
additionalContext: recall
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function outputPromptBootstrap(input, fallbackEvent) {
|
|
131
|
+
const cwd = lib.resolveProjectDir(input, projectEnv);
|
|
132
|
+
const bootstrap = bootstrapContext(cwd);
|
|
133
|
+
const recall = runRecallHook(input);
|
|
134
|
+
const parts = [];
|
|
135
|
+
if (bootstrap && typeof bootstrap.content === 'string' && bootstrap.content.trim().length > 0) {
|
|
136
|
+
parts.push(bootstrap.content.trim());
|
|
137
|
+
}
|
|
138
|
+
if (recall) {
|
|
139
|
+
parts.push(recall);
|
|
140
|
+
}
|
|
141
|
+
if (parts.length === 0) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const combined = parts.join('\n\n');
|
|
145
|
+
const hookName = hookEventName(input, fallbackEvent);
|
|
146
|
+
jsonOutput({
|
|
147
|
+
context: combined,
|
|
148
|
+
degraded: bootstrap ? bootstrap.degraded : false,
|
|
149
|
+
source: bootstrap ? bootstrap.source : 'recall',
|
|
150
|
+
warnings: bootstrap && Array.isArray(bootstrap.warnings) ? bootstrap.warnings : [],
|
|
151
|
+
hookSpecificOutput: {
|
|
152
|
+
hookEventName: hookName,
|
|
153
|
+
additionalContext: combined
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function evaluatePreToolUse(input) {
|
|
159
|
+
const cwd = lib.resolveProjectDir(input, projectEnv);
|
|
160
|
+
const payload = JSON.stringify(input);
|
|
161
|
+
const args = lib.appendHomeArg(['policy', 'evaluate-pre-tool-use', '--host', host, '--cwd', cwd, '--json']);
|
|
162
|
+
const cliResult = lib.spawnNativeSoul(args, { input: payload });
|
|
163
|
+
const result = lib.parseJsonStdout(cliResult) || policy.evaluate(input);
|
|
164
|
+
const output = lib.buildPreToolUseHookOutput(result);
|
|
165
|
+
if (output) {
|
|
166
|
+
jsonOutput({
|
|
167
|
+
...output,
|
|
168
|
+
hookSpecificOutput: {
|
|
169
|
+
...output.hookSpecificOutput,
|
|
170
|
+
hookEventName: hookEventName(input, 'PreToolUse')
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function flushMemory(input) {
|
|
177
|
+
const cwd = lib.resolveProjectDir(input, projectEnv);
|
|
178
|
+
const args = lib.appendHomeArg(['memory', 'flush-hook', '--host', host, '--cwd', cwd, '--json']);
|
|
179
|
+
const result = lib.spawnNativeSoul(args, { input: JSON.stringify(input) });
|
|
180
|
+
const json = lib.parseJsonStdout(result);
|
|
181
|
+
if (json) {
|
|
182
|
+
jsonOutput({ nativeSoul: json });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
lib.runNonBlocking(async () => {
|
|
187
|
+
const input = await lib.readStdin({ fallback: (raw) => ({ raw }) });
|
|
188
|
+
if (kind === 'session-start') {
|
|
189
|
+
outputBootstrapContext(input, 'SessionStart');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (kind === 'prompt-bootstrap') {
|
|
193
|
+
outputPromptBootstrap(input, 'UserPromptSubmit');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (kind === 'recall') {
|
|
197
|
+
recallContext(input);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (kind === 'pre-tool-use') {
|
|
201
|
+
evaluatePreToolUse(input);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (kind === 'memory-flush') {
|
|
205
|
+
flushMemory(input);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('node:child_process');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_HOOK_TIMEOUT_MS = 9000;
|
|
8
|
+
const PROJECT_CACHE_DIR = '.nativesoul-cache';
|
|
9
|
+
|
|
10
|
+
function claudeDesktopConfigPath(homeDir) {
|
|
11
|
+
if (process.platform === 'darwin') {
|
|
12
|
+
return path.join(homeDir, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
13
|
+
}
|
|
14
|
+
if (process.platform === 'win32') {
|
|
15
|
+
const appData = process.env.APPDATA && process.env.APPDATA.trim().length > 0
|
|
16
|
+
? process.env.APPDATA
|
|
17
|
+
: path.join(homeDir, 'AppData', 'Roaming');
|
|
18
|
+
return path.join(appData, 'Claude', 'claude_desktop_config.json');
|
|
19
|
+
}
|
|
20
|
+
const configHome = process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME.trim().length > 0
|
|
21
|
+
? process.env.XDG_CONFIG_HOME
|
|
22
|
+
: path.join(homeDir, '.config');
|
|
23
|
+
return path.join(configHome, 'Claude', 'claude_desktop_config.json');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function defaultMcpConfigCandidates(homeDir) {
|
|
27
|
+
return [
|
|
28
|
+
path.join(homeDir, '.claude', 'mcp.json'),
|
|
29
|
+
path.join(homeDir, '.claude.json'),
|
|
30
|
+
path.join(homeDir, '.codex', 'mcp.json'),
|
|
31
|
+
path.join(homeDir, '.gemini', 'settings.json'),
|
|
32
|
+
path.join(homeDir, '.gemini', 'mcp.json'),
|
|
33
|
+
path.join(homeDir, '.gemini', 'config', 'mcp_config.json'),
|
|
34
|
+
path.join(homeDir, '.gemini', 'antigravity-cli', 'mcp_config.json'),
|
|
35
|
+
path.join(homeDir, '.gemini', 'antigravity', 'mcp_config.json'),
|
|
36
|
+
path.join(homeDir, '.gemini', 'antigravity-ide', 'mcp_config.json'),
|
|
37
|
+
path.join(homeDir, '.grok', 'mcp.json'),
|
|
38
|
+
path.join(homeDir, '.antigravity', 'mcp.json'),
|
|
39
|
+
claudeDesktopConfigPath(homeDir)
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveNativeSoulCli(options = {}) {
|
|
44
|
+
if (process.env.NATIVESOUL_CLI) {
|
|
45
|
+
return process.env.NATIVESOUL_CLI;
|
|
46
|
+
}
|
|
47
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
48
|
+
if (!homeDir) {
|
|
49
|
+
return 'nativesoul';
|
|
50
|
+
}
|
|
51
|
+
const candidates = options.configPaths ?? defaultMcpConfigCandidates(homeDir);
|
|
52
|
+
for (const configPath of candidates) {
|
|
53
|
+
try {
|
|
54
|
+
if (fs.existsSync(configPath)) {
|
|
55
|
+
const content = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
56
|
+
const mcpCommand = content?.mcpServers?.['nativesoul']?.command;
|
|
57
|
+
if (typeof mcpCommand === 'string' && mcpCommand.includes('nativesoul-mcp')) {
|
|
58
|
+
const cliCommand = mcpCommand.replace('nativesoul-mcp', 'nativesoul');
|
|
59
|
+
if (fs.existsSync(cliCommand)) {
|
|
60
|
+
return cliCommand;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// Ignore malformed MCP config.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return 'nativesoul';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readStdin(options = {}) {
|
|
72
|
+
const fallback = options.fallback ?? (() => ({}));
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
let data = '';
|
|
75
|
+
process.stdin.setEncoding('utf8');
|
|
76
|
+
process.stdin.on('data', (chunk) => {
|
|
77
|
+
data += chunk;
|
|
78
|
+
});
|
|
79
|
+
process.stdin.on('end', () => {
|
|
80
|
+
if (data.trim().length === 0) {
|
|
81
|
+
resolve({});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
resolve(JSON.parse(data));
|
|
86
|
+
} catch {
|
|
87
|
+
resolve(fallback(data));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function resolveProjectDir(input, envVarName) {
|
|
94
|
+
const common = input && !Array.isArray(input) && typeof input === 'object'
|
|
95
|
+
? input.common
|
|
96
|
+
: null;
|
|
97
|
+
const commonWorkspace = common && !Array.isArray(common) && typeof common === 'object'
|
|
98
|
+
? common.workspaceRoot || common.workspace_root || (Array.isArray(common.workspacePaths) ? common.workspacePaths[0] : null) || (Array.isArray(common.workspace_paths) ? common.workspace_paths[0] : null)
|
|
99
|
+
: null;
|
|
100
|
+
return input.cwd ||
|
|
101
|
+
input.workspaceRoot ||
|
|
102
|
+
input.workspace_root ||
|
|
103
|
+
input.project_dir ||
|
|
104
|
+
commonWorkspace ||
|
|
105
|
+
process.env.NATIVESOUL_CWD ||
|
|
106
|
+
process.env[envVarName] ||
|
|
107
|
+
process.env.CLAUDE_PROJECT_DIR ||
|
|
108
|
+
process.env.GROK_WORKSPACE_ROOT ||
|
|
109
|
+
process.env.GEMINI_CWD ||
|
|
110
|
+
process.env.GEMINI_PROJECT_DIR ||
|
|
111
|
+
process.cwd();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function appendHomeArg(args) {
|
|
115
|
+
const home = process.env.NATIVESOUL_HOME;
|
|
116
|
+
if (home && home.trim().length > 0) {
|
|
117
|
+
args.push('--home', home.trim());
|
|
118
|
+
}
|
|
119
|
+
return args;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function spawnNativeSoul(args, options = {}) {
|
|
123
|
+
const command = resolveNativeSoulCli(options.resolveOptions);
|
|
124
|
+
const isNodeScript = /\.(?:cjs|mjs|js)$/iu.test(command);
|
|
125
|
+
const spawnCommand = isNodeScript ? process.execPath : command;
|
|
126
|
+
const spawnArgs = isNodeScript ? [command, ...args] : args;
|
|
127
|
+
const shell = process.platform === 'win32' && /\.(?:cmd|bat)$/iu.test(spawnCommand);
|
|
128
|
+
return spawnSync(spawnCommand, spawnArgs, {
|
|
129
|
+
input: options.input,
|
|
130
|
+
encoding: 'utf8',
|
|
131
|
+
timeout: options.timeout ?? DEFAULT_HOOK_TIMEOUT_MS,
|
|
132
|
+
env: process.env,
|
|
133
|
+
stdio: options.stdio ?? ['pipe', 'pipe', 'pipe'],
|
|
134
|
+
shell
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function projectFallbackBootstrapHome(cwd) {
|
|
139
|
+
return path.join(cwd, PROJECT_CACHE_DIR);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function projectBootstrapCachePath(cwd, host) {
|
|
143
|
+
return path.join(projectFallbackBootstrapHome(cwd), 'generated', host, 'last-context.md');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function hasCacheWriteSkippedWarning(context) {
|
|
147
|
+
return Array.isArray(context?.warnings) &&
|
|
148
|
+
context.warnings.some((warning) => typeof warning === 'string' && warning.startsWith('BOOTSTRAP_CACHE_WRITE_SKIPPED:'));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function removeCacheWriteSkippedWarning(content) {
|
|
152
|
+
const withoutSkipped = content.replace(/^warnings: (.+)$/mu, (_line, value) => {
|
|
153
|
+
const warnings = String(value)
|
|
154
|
+
.split(',')
|
|
155
|
+
.map((item) => item.trim())
|
|
156
|
+
.filter((item) => item.length > 0 && !item.startsWith('BOOTSTRAP_CACHE_WRITE_SKIPPED:'));
|
|
157
|
+
return warnings.length > 0 ? `warnings: ${warnings.join(', ')}` : 'warnings: none';
|
|
158
|
+
});
|
|
159
|
+
return withoutSkipped.replace(/^degraded: true$/mu, 'degraded: false');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function writeProjectFallbackCache(input) {
|
|
163
|
+
const { context, cwd, host } = input;
|
|
164
|
+
if (!context || typeof context.content !== 'string' || !hasCacheWriteSkippedWarning(context)) {
|
|
165
|
+
return context;
|
|
166
|
+
}
|
|
167
|
+
const sanitizedContent = removeCacheWriteSkippedWarning(context.content);
|
|
168
|
+
try {
|
|
169
|
+
const targetPath = projectBootstrapCachePath(cwd, host);
|
|
170
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true, mode: 0o700 });
|
|
171
|
+
const tempPath = path.join(path.dirname(targetPath), `.last-context-${Date.now()}-${process.pid}.tmp`);
|
|
172
|
+
fs.writeFileSync(tempPath, sanitizedContent, { encoding: 'utf8', mode: 0o600 });
|
|
173
|
+
fs.renameSync(tempPath, targetPath);
|
|
174
|
+
const warnings = Array.isArray(context.warnings)
|
|
175
|
+
? context.warnings.filter((warning) => typeof warning !== 'string' || !warning.startsWith('BOOTSTRAP_CACHE_WRITE_SKIPPED:'))
|
|
176
|
+
: [];
|
|
177
|
+
return {
|
|
178
|
+
...context,
|
|
179
|
+
degraded: false,
|
|
180
|
+
warnings,
|
|
181
|
+
content: sanitizedContent,
|
|
182
|
+
projectFallbackCachePath: targetPath
|
|
183
|
+
};
|
|
184
|
+
} catch {
|
|
185
|
+
return context;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function readProjectFallbackCache(input) {
|
|
190
|
+
try {
|
|
191
|
+
const targetPath = projectBootstrapCachePath(input.cwd, input.host);
|
|
192
|
+
if (!fs.existsSync(targetPath)) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const cachedContent = fs.readFileSync(targetPath, 'utf8');
|
|
196
|
+
const content = [
|
|
197
|
+
'---',
|
|
198
|
+
'nativesoul: bootstrap-context-cache',
|
|
199
|
+
`host: ${input.host}`,
|
|
200
|
+
`generated_at: ${new Date().toISOString()}`,
|
|
201
|
+
'degraded: true',
|
|
202
|
+
'warnings: PROJECT_BOOTSTRAP_CACHE_FALLBACK',
|
|
203
|
+
'---',
|
|
204
|
+
'',
|
|
205
|
+
cachedContent
|
|
206
|
+
].join('\n');
|
|
207
|
+
return {
|
|
208
|
+
content,
|
|
209
|
+
degraded: true,
|
|
210
|
+
warnings: ['PROJECT_BOOTSTRAP_CACHE_FALLBACK'],
|
|
211
|
+
source: 'nativesoul-project-cache',
|
|
212
|
+
targetPath
|
|
213
|
+
};
|
|
214
|
+
} catch {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function parseJsonStdout(result) {
|
|
220
|
+
if (result.status !== 0 || !result.stdout.trim()) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
return JSON.parse(result.stdout);
|
|
225
|
+
} catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function buildPreToolUseHookOutput(result) {
|
|
231
|
+
if (!result || result.blocked !== true) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
decision: 'block',
|
|
236
|
+
reason: result.message,
|
|
237
|
+
blocked: true,
|
|
238
|
+
hookSpecificOutput: {
|
|
239
|
+
hookEventName: 'PreToolUse',
|
|
240
|
+
permissionDecision: 'deny',
|
|
241
|
+
permissionDecisionReason: result.message
|
|
242
|
+
},
|
|
243
|
+
nativeSoulPolicy: result
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function runNonBlocking(main, timeoutMs = DEFAULT_HOOK_TIMEOUT_MS) {
|
|
248
|
+
const timer = setTimeout(() => {
|
|
249
|
+
process.exit(0);
|
|
250
|
+
}, timeoutMs + 1000);
|
|
251
|
+
timer.unref();
|
|
252
|
+
main()
|
|
253
|
+
.then(() => {
|
|
254
|
+
clearTimeout(timer);
|
|
255
|
+
process.exitCode = 0;
|
|
256
|
+
})
|
|
257
|
+
.catch(() => {
|
|
258
|
+
clearTimeout(timer);
|
|
259
|
+
process.exitCode = 0;
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
module.exports = {
|
|
264
|
+
DEFAULT_HOOK_TIMEOUT_MS,
|
|
265
|
+
appendHomeArg,
|
|
266
|
+
buildPreToolUseHookOutput,
|
|
267
|
+
claudeDesktopConfigPath,
|
|
268
|
+
defaultMcpConfigCandidates,
|
|
269
|
+
parseJsonStdout,
|
|
270
|
+
projectBootstrapCachePath,
|
|
271
|
+
projectFallbackBootstrapHome,
|
|
272
|
+
readStdin,
|
|
273
|
+
readProjectFallbackCache,
|
|
274
|
+
resolveNativeSoulCli,
|
|
275
|
+
resolveProjectDir,
|
|
276
|
+
runNonBlocking,
|
|
277
|
+
spawnNativeSoul,
|
|
278
|
+
writeProjectFallbackCache
|
|
279
|
+
};
|