@aria_asi/cli 0.2.26 → 0.2.30
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/CLIENT-ONBOARDING.md +282 -0
- package/bin/aria.js +1140 -14
- package/dist/aria-connector/src/auth-commands.d.ts +1 -0
- package/dist/aria-connector/src/auth-commands.d.ts.map +1 -1
- package/dist/aria-connector/src/auth-commands.js +89 -41
- package/dist/aria-connector/src/auth-commands.js.map +1 -1
- package/dist/aria-connector/src/chat.d.ts +3 -0
- package/dist/aria-connector/src/chat.d.ts.map +1 -1
- package/dist/aria-connector/src/chat.js +146 -8
- package/dist/aria-connector/src/chat.js.map +1 -1
- package/dist/aria-connector/src/codebase-scanner.d.ts +2 -2
- package/dist/aria-connector/src/codebase-scanner.d.ts.map +1 -1
- package/dist/aria-connector/src/codebase-scanner.js +1 -1
- package/dist/aria-connector/src/codebase-scanner.js.map +1 -1
- package/dist/aria-connector/src/config.d.ts +12 -0
- package/dist/aria-connector/src/config.d.ts.map +1 -1
- package/dist/aria-connector/src/config.js +2 -0
- package/dist/aria-connector/src/config.js.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.js +80 -24
- package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
- package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +37 -0
- package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/codebase-awareness.js +335 -0
- package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -0
- package/dist/aria-connector/src/connectors/codex.d.ts +3 -0
- package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/codex.js +248 -0
- package/dist/aria-connector/src/connectors/codex.js.map +1 -0
- package/dist/aria-connector/src/connectors/cognitive-skills.d.ts +2 -0
- package/dist/aria-connector/src/connectors/cognitive-skills.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/cognitive-skills.js +47 -0
- package/dist/aria-connector/src/connectors/cognitive-skills.js.map +1 -0
- package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.js +90 -4
- package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
- package/dist/aria-connector/src/connectors/repo-git-hooks.d.ts +3 -0
- package/dist/aria-connector/src/connectors/repo-git-hooks.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/repo-git-hooks.js +87 -0
- package/dist/aria-connector/src/connectors/repo-git-hooks.js.map +1 -0
- package/dist/aria-connector/src/connectors/repo-guard.d.ts +19 -0
- package/dist/aria-connector/src/connectors/repo-guard.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/repo-guard.js +509 -0
- package/dist/aria-connector/src/connectors/repo-guard.js.map +1 -0
- package/dist/aria-connector/src/connectors/runtime.d.ts +2 -0
- package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/runtime.js +330 -0
- package/dist/aria-connector/src/connectors/runtime.js.map +1 -0
- package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/shell.js +78 -13
- package/dist/aria-connector/src/connectors/shell.js.map +1 -1
- package/dist/aria-connector/src/connectors/syncd.d.ts +27 -0
- package/dist/aria-connector/src/connectors/syncd.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/syncd.js +405 -0
- package/dist/aria-connector/src/connectors/syncd.js.map +1 -0
- package/dist/aria-connector/src/decisions.d.ts +207 -0
- package/dist/aria-connector/src/decisions.d.ts.map +1 -0
- package/dist/aria-connector/src/decisions.js +291 -0
- package/dist/aria-connector/src/decisions.js.map +1 -0
- package/dist/aria-connector/src/garden-control-plane.d.ts.map +1 -1
- package/dist/aria-connector/src/garden-control-plane.js +74 -17
- package/dist/aria-connector/src/garden-control-plane.js.map +1 -1
- package/dist/aria-connector/src/github-connect.d.ts +18 -0
- package/dist/aria-connector/src/github-connect.d.ts.map +1 -0
- package/dist/aria-connector/src/github-connect.js +117 -0
- package/dist/aria-connector/src/github-connect.js.map +1 -0
- package/dist/aria-connector/src/harness-client.d.ts +15 -0
- package/dist/aria-connector/src/harness-client.d.ts.map +1 -1
- package/dist/aria-connector/src/harness-client.js +106 -3
- package/dist/aria-connector/src/harness-client.js.map +1 -1
- package/dist/aria-connector/src/hive-client.d.ts +30 -0
- package/dist/aria-connector/src/hive-client.d.ts.map +1 -1
- package/dist/aria-connector/src/hive-client.js +124 -5
- package/dist/aria-connector/src/hive-client.js.map +1 -1
- package/dist/aria-connector/src/index.d.ts +13 -2
- package/dist/aria-connector/src/index.d.ts.map +1 -1
- package/dist/aria-connector/src/index.js +10 -1
- package/dist/aria-connector/src/index.js.map +1 -1
- package/dist/aria-connector/src/lib/aristotle-noor-wire.d.ts +102 -0
- package/dist/aria-connector/src/lib/aristotle-noor-wire.d.ts.map +1 -0
- package/dist/aria-connector/src/lib/aristotle-noor-wire.js +231 -0
- package/dist/aria-connector/src/lib/aristotle-noor-wire.js.map +1 -0
- package/dist/aria-connector/src/providers/types.d.ts +5 -0
- package/dist/aria-connector/src/providers/types.d.ts.map +1 -1
- package/dist/aria-connector/src/runtime-proof.d.ts +45 -0
- package/dist/aria-connector/src/runtime-proof.d.ts.map +1 -0
- package/dist/aria-connector/src/runtime-proof.js +340 -0
- package/dist/aria-connector/src/runtime-proof.js.map +1 -0
- package/dist/aria-connector/src/self-update.d.ts +2 -1
- package/dist/aria-connector/src/self-update.d.ts.map +1 -1
- package/dist/aria-connector/src/self-update.js +84 -8
- package/dist/aria-connector/src/self-update.js.map +1 -1
- package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
- package/dist/aria-connector/src/setup-wizard.js +34 -2
- package/dist/aria-connector/src/setup-wizard.js.map +1 -1
- package/dist/assets/hooks/aria-agent-handoff.mjs +224 -0
- package/dist/assets/hooks/aria-agent-ledger-merge.mjs +164 -0
- package/dist/assets/hooks/aria-architect-fallback.mjs +267 -0
- package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +668 -0
- package/dist/assets/hooks/aria-discovery-record.mjs +101 -0
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +412 -0
- package/dist/assets/hooks/aria-import-resolution-gate.mjs +330 -0
- package/dist/assets/hooks/aria-outcome-record.mjs +84 -0
- package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +294 -0
- package/dist/assets/hooks/aria-pre-text-gate.mjs +112 -0
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +2133 -0
- package/dist/assets/hooks/aria-preprompt-consult.mjs +438 -0
- package/dist/assets/hooks/aria-preturn-memory-gate.mjs +570 -0
- package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +397 -0
- package/dist/assets/hooks/aria-stop-gate.mjs +1551 -0
- package/dist/assets/hooks/aria-trigger-autolearn.mjs +229 -0
- package/dist/assets/hooks/aria-userprompt-abandon-detect.mjs +192 -0
- package/dist/assets/hooks/doctrine_trigger_map.json +479 -0
- package/dist/assets/hooks/lib/canonical-lenses.mjs +64 -0
- package/dist/assets/hooks/lib/gate-audit.mjs +43 -0
- package/dist/assets/hooks/test-aria-preturn-memory-gate.mjs +245 -0
- package/dist/assets/hooks/test-tier-lens-labeling.mjs +399 -0
- package/dist/assets/opencode-plugins/harness-context/index.js +60 -0
- package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +179 -0
- package/dist/assets/opencode-plugins/harness-context/package.json +9 -0
- package/dist/assets/opencode-plugins/harness-gate/index.js +248 -0
- package/dist/assets/opencode-plugins/harness-outcome/index.js +129 -0
- package/dist/assets/opencode-plugins/harness-role/index.js +77 -0
- package/dist/assets/opencode-plugins/harness-role/package.json +9 -0
- package/dist/assets/opencode-plugins/harness-stop/index.js +241 -0
- package/dist/runtime/discipline/CLAUDE.md +339 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +63 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/domain-matrix.md +80 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/evolution-loop.md +30 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/readable-cognition.md +27 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +35 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/references/checklist.md +31 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +39 -0
- package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +43 -0
- package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +38 -0
- package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +26 -0
- package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +35 -0
- package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +72 -0
- package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +38 -0
- package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +38 -0
- package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +25 -0
- package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +26 -0
- package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +25 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +81 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +98 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +99 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +127 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +117 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +112 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +102 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +121 -0
- package/dist/runtime/doctor.mjs +23 -0
- package/dist/runtime/local-phase.mjs +650 -0
- package/dist/runtime/manifest.json +15 -0
- package/dist/runtime/mizan-scheduler.mjs +331 -0
- package/dist/runtime/package.json +6 -0
- package/dist/runtime/provider-proxy.mjs +594 -0
- package/dist/runtime/sdk/BUNDLED.json +5 -0
- package/dist/runtime/sdk/index.d.ts +477 -0
- package/dist/runtime/sdk/index.js +1469 -0
- package/dist/runtime/sdk/index.js.map +1 -0
- package/dist/runtime/sdk/package.json +8 -0
- package/dist/runtime/sdk/runWithCognition.d.ts +77 -0
- package/dist/runtime/sdk/runWithCognition.js +157 -0
- package/dist/runtime/sdk/runWithCognition.js.map +1 -0
- package/dist/runtime/service.mjs +3058 -0
- package/dist/runtime/vendor/aria-gate-runtime/index.d.ts +53 -0
- package/dist/runtime/vendor/aria-gate-runtime/index.d.ts.map +1 -0
- package/dist/runtime/vendor/aria-gate-runtime/index.js +292 -0
- package/dist/runtime/vendor/aria-gate-runtime/index.js.map +1 -0
- package/dist/runtime/vendor/aria-gate-runtime/package.json +6 -0
- package/dist/sdk/BUNDLED.json +2 -2
- package/dist/sdk/index.d.ts +283 -0
- package/dist/sdk/index.js +622 -85
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/runWithCognition.d.ts +77 -0
- package/dist/sdk/runWithCognition.js +157 -0
- package/dist/sdk/runWithCognition.js.map +1 -0
- package/hooks/aria-agent-handoff.mjs +11 -1
- package/hooks/aria-architect-fallback.mjs +109 -40
- package/hooks/aria-cognition-substrate-binding.mjs +668 -0
- package/hooks/aria-harness-via-sdk.mjs +34 -21
- package/hooks/aria-import-resolution-gate.mjs +330 -0
- package/hooks/aria-outcome-record.mjs +5 -1
- package/hooks/aria-pre-emit-dryrun.mjs +294 -0
- package/hooks/aria-pre-tool-gate.mjs +828 -41
- package/hooks/aria-preprompt-consult.mjs +113 -13
- package/hooks/aria-preturn-memory-gate.mjs +298 -6
- package/hooks/aria-repo-doctrine-gate.mjs +397 -0
- package/hooks/aria-stop-gate.mjs +739 -76
- package/hooks/aria-userprompt-abandon-detect.mjs +5 -1
- package/hooks/doctrine_trigger_map.json +209 -15
- package/hooks/lib/canonical-lenses.mjs +64 -0
- package/hooks/lib/gate-audit.mjs +43 -0
- package/opencode-plugins/harness-context/index.js +1 -1
- package/opencode-plugins/harness-context/inject-context.mjs +82 -23
- package/opencode-plugins/harness-gate/index.js +248 -0
- package/opencode-plugins/harness-outcome/index.js +129 -0
- package/opencode-plugins/harness-stop/index.js +241 -0
- package/package.json +9 -3
- package/runtime-src/doctor.mjs +23 -0
- package/runtime-src/local-phase.mjs +650 -0
- package/runtime-src/mizan-scheduler.mjs +331 -0
- package/runtime-src/provider-proxy.mjs +594 -0
- package/runtime-src/service.mjs +3058 -0
- package/scripts/bundle-sdk.mjs +317 -0
- package/scripts/install-client.sh +176 -0
- package/scripts/publish-all.sh +344 -0
- package/scripts/publish-docker.sh +27 -0
- package/scripts/validate-hook-contracts.mjs +54 -0
- package/scripts/validate-skill-prompts.mjs +95 -0
- package/skills/aria-cognition/aria-essence/SKILL.md +63 -0
- package/skills/aria-cognition/aria-essence/references/domain-matrix.md +80 -0
- package/skills/aria-cognition/aria-essence/references/evolution-loop.md +30 -0
- package/skills/aria-cognition/aria-essence/references/readable-cognition.md +27 -0
- package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +35 -0
- package/skills/aria-cognition/aria-forge-guardrails/references/checklist.md +31 -0
- package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +39 -0
- package/skills/aria-cognition/forge-quality-rules/SKILL.md +43 -0
- package/skills/aria-cognition/ghazali-8lens/SKILL.md +38 -0
- package/skills/aria-cognition/istiqra-induction/SKILL.md +26 -0
- package/skills/aria-cognition/ladunni-22/SKILL.md +35 -0
- package/skills/aria-cognition/mizan/SKILL.md +72 -0
- package/skills/aria-cognition/nadia/SKILL.md +38 -0
- package/skills/aria-cognition/nadia-psi/SKILL.md +38 -0
- package/skills/aria-cognition/predictor/SKILL.md +25 -0
- package/skills/aria-cognition/qiyas-analogy/SKILL.md +26 -0
- package/skills/aria-cognition/soul-domains/SKILL.md +25 -0
- package/src/auth-commands.ts +111 -45
- package/src/chat.ts +174 -13
- package/src/codebase-scanner.ts +4 -0
- package/src/config.ts +15 -0
- package/src/connectors/claude-code.ts +79 -25
- package/src/connectors/codebase-awareness.ts +408 -0
- package/src/connectors/codex.ts +274 -0
- package/src/connectors/cognitive-skills.ts +51 -0
- package/src/connectors/opencode.ts +93 -4
- package/src/connectors/repo-git-hooks.ts +86 -0
- package/src/connectors/repo-guard.ts +589 -0
- package/src/connectors/runtime.ts +374 -0
- package/src/connectors/shell.ts +83 -14
- package/src/connectors/syncd.ts +488 -0
- package/src/decisions.ts +469 -0
- package/src/garden-control-plane.ts +101 -26
- package/src/github-connect.ts +143 -0
- package/src/harness-client.ts +128 -3
- package/src/hive-client.ts +165 -5
- package/src/index.ts +41 -2
- package/src/lib/aristotle-noor-wire.ts +310 -0
- package/src/providers/types.ts +6 -0
- package/src/runtime-proof.ts +392 -0
- package/src/self-update.ts +89 -8
- package/src/setup-wizard.ts +37 -2
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import { execFileSync } from 'child_process';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import {
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
statSync,
|
|
10
|
+
renameSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from 'fs';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import type { AriaConfig } from '../config.js';
|
|
17
|
+
import { loadConfig } from '../config.js';
|
|
18
|
+
|
|
19
|
+
const ARIA_DIR = path.join(homedir(), '.aria');
|
|
20
|
+
const BIN_DIR = path.join(ARIA_DIR, 'bin');
|
|
21
|
+
const ENV_PATH = path.join(ARIA_DIR, 'repo-guard.env');
|
|
22
|
+
const STATE_PATH = path.join(ARIA_DIR, 'repo-guard-state.json');
|
|
23
|
+
const EVENTS_PATH = path.join(ARIA_DIR, 'repo-guard-events.jsonl');
|
|
24
|
+
const LAST_ALERT_PATH = path.join(ARIA_DIR, 'repo-guard-last-alert.json');
|
|
25
|
+
const SHADOW_ROOT = path.join(ARIA_DIR, 'repo-guard', 'shadow');
|
|
26
|
+
const QUARANTINE_ROOT = path.join(ARIA_DIR, 'repo-guard', 'quarantine');
|
|
27
|
+
const DEFAULT_DEBOUNCE_MS = Number(process.env.ARIA_REPO_GUARD_DEBOUNCE_MS || 350);
|
|
28
|
+
const BLOCKING_MODE = !/^(0|false|no)$/i.test(process.env.ARIA_REPO_GUARD_BLOCKING || 'true');
|
|
29
|
+
const DOCTRINE_PREFIXES = ['apps/', 'packages/', 'harness/', 'ops/', 'scripts/'];
|
|
30
|
+
const DOCTRINE_EXTENSIONS = new Set([
|
|
31
|
+
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
|
|
32
|
+
'.py', '.sh', '.bash', '.zsh', '.rb', '.go',
|
|
33
|
+
'.rs', '.java', '.kt', '.cs', '.php', '.swift',
|
|
34
|
+
'.scala', '.yml', '.yaml',
|
|
35
|
+
]);
|
|
36
|
+
const IGNORED_SEGMENTS = new Set([
|
|
37
|
+
'.git',
|
|
38
|
+
'node_modules',
|
|
39
|
+
'dist',
|
|
40
|
+
'build',
|
|
41
|
+
'.next',
|
|
42
|
+
'.turbo',
|
|
43
|
+
'.cache',
|
|
44
|
+
'coverage',
|
|
45
|
+
'archive',
|
|
46
|
+
'generated',
|
|
47
|
+
'telegram_env',
|
|
48
|
+
'backup-opencode-config',
|
|
49
|
+
'training-data',
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
type GuardStatus = 'idle' | 'watching' | 'violation' | 'error';
|
|
53
|
+
|
|
54
|
+
interface GuardState {
|
|
55
|
+
status: GuardStatus;
|
|
56
|
+
watchedRepos: string[];
|
|
57
|
+
lastEvent: string | null;
|
|
58
|
+
lastViolation: string | null;
|
|
59
|
+
lastError: string | null;
|
|
60
|
+
updatedAt: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface RepoGuardOptions {
|
|
64
|
+
repos?: string[];
|
|
65
|
+
debounceMs?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface AlertPayload {
|
|
69
|
+
title: string;
|
|
70
|
+
message: string;
|
|
71
|
+
repoRoot?: string;
|
|
72
|
+
relPath?: string;
|
|
73
|
+
action?: string;
|
|
74
|
+
detail?: string;
|
|
75
|
+
type: 'violation' | 'blocked-delete' | 'watcher-error' | 'status';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function writeState(update: Partial<GuardState>): GuardState {
|
|
79
|
+
const current = readGuardState();
|
|
80
|
+
const next: GuardState = {
|
|
81
|
+
...current,
|
|
82
|
+
...update,
|
|
83
|
+
updatedAt: new Date().toISOString(),
|
|
84
|
+
};
|
|
85
|
+
mkdirSync(ARIA_DIR, { recursive: true, mode: 0o700 });
|
|
86
|
+
writeFileSync(STATE_PATH, JSON.stringify(next, null, 2) + '\n', { mode: 0o600 });
|
|
87
|
+
return next;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function readGuardState(): GuardState {
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(readFileSync(STATE_PATH, 'utf8')) as Partial<GuardState>;
|
|
93
|
+
return {
|
|
94
|
+
status: parsed.status || 'idle',
|
|
95
|
+
watchedRepos: Array.isArray(parsed.watchedRepos) ? parsed.watchedRepos : [],
|
|
96
|
+
lastEvent: parsed.lastEvent || null,
|
|
97
|
+
lastViolation: parsed.lastViolation || null,
|
|
98
|
+
lastError: parsed.lastError || null,
|
|
99
|
+
updatedAt: parsed.updatedAt || new Date(0).toISOString(),
|
|
100
|
+
};
|
|
101
|
+
} catch {
|
|
102
|
+
return {
|
|
103
|
+
status: 'idle',
|
|
104
|
+
watchedRepos: [],
|
|
105
|
+
lastEvent: null,
|
|
106
|
+
lastViolation: null,
|
|
107
|
+
lastError: null,
|
|
108
|
+
updatedAt: new Date(0).toISOString(),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function appendEvent(payload: Record<string, unknown>): void {
|
|
114
|
+
mkdirSync(ARIA_DIR, { recursive: true, mode: 0o700 });
|
|
115
|
+
writeFileSync(EVENTS_PATH, JSON.stringify({
|
|
116
|
+
ts: new Date().toISOString(),
|
|
117
|
+
...payload,
|
|
118
|
+
}) + '\n', { mode: 0o600, flag: 'a' });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function writeAlert(payload: AlertPayload): void {
|
|
122
|
+
mkdirSync(ARIA_DIR, { recursive: true, mode: 0o700 });
|
|
123
|
+
writeFileSync(LAST_ALERT_PATH, JSON.stringify({
|
|
124
|
+
ts: new Date().toISOString(),
|
|
125
|
+
...payload,
|
|
126
|
+
}, null, 2) + '\n', { mode: 0o600 });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function tryDesktopNotify(title: string, message: string): void {
|
|
130
|
+
try {
|
|
131
|
+
execFileSync('notify-send', [title, message], { stdio: 'ignore' });
|
|
132
|
+
} catch {}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function emitAlert(payload: AlertPayload): void {
|
|
136
|
+
const lines = [
|
|
137
|
+
`[aria-repo-guard] ${payload.title}`,
|
|
138
|
+
payload.repoRoot && payload.relPath ? `path=${payload.repoRoot}:${payload.relPath}` : null,
|
|
139
|
+
payload.action ? `action=${payload.action}` : null,
|
|
140
|
+
payload.detail ? `detail=${payload.detail}` : null,
|
|
141
|
+
payload.message,
|
|
142
|
+
].filter(Boolean);
|
|
143
|
+
|
|
144
|
+
writeAlert(payload);
|
|
145
|
+
console.error(lines.join('\n'));
|
|
146
|
+
tryDesktopNotify(payload.title, payload.repoRoot && payload.relPath
|
|
147
|
+
? `${payload.relPath}\n${payload.message}`
|
|
148
|
+
: payload.message);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function writeWrapper(binPath: string, args: string[]): void {
|
|
152
|
+
const ariaBin = path.join(connectorPackageRoot(), 'bin', 'aria.js');
|
|
153
|
+
writeFileSync(
|
|
154
|
+
binPath,
|
|
155
|
+
`#!/usr/bin/env bash\nexec node "${ariaBin}" ${args.join(' ')} "$@"\n`,
|
|
156
|
+
{ mode: 0o755 },
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function connectorPackageRoot(): string {
|
|
161
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
162
|
+
const candidates = [
|
|
163
|
+
path.resolve(here, '..', '..'),
|
|
164
|
+
path.resolve(here, '..', '..', '..', '..'),
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
for (const candidate of candidates) {
|
|
168
|
+
if (existsSync(path.join(candidate, 'bin', 'aria.js')) && existsSync(path.join(candidate, 'package.json'))) {
|
|
169
|
+
return candidate;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return candidates[candidates.length - 1];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function normalizeRel(input: string): string {
|
|
177
|
+
return input.split(path.sep).join('/');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function repoHash(repoRoot: string): string {
|
|
181
|
+
return createHash('sha256').update(repoRoot).digest('hex').slice(0, 16);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function shadowPath(repoRoot: string, relPath: string): string {
|
|
185
|
+
return path.join(SHADOW_ROOT, repoHash(repoRoot), relPath);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function quarantinePath(repoRoot: string, relPath: string): string {
|
|
189
|
+
const stamped = `${Date.now()}-${relPath.replace(/[\\/]/g, '__')}`;
|
|
190
|
+
return path.join(QUARANTINE_ROOT, repoHash(repoRoot), stamped);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function ensureParent(filePath: string): void {
|
|
194
|
+
mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o755 });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function looksLikeRepoRoot(candidate: string): boolean {
|
|
198
|
+
return existsSync(path.join(candidate, '.git'));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function findRepoRoot(startDir: string): string | null {
|
|
202
|
+
let current = path.resolve(startDir);
|
|
203
|
+
while (true) {
|
|
204
|
+
if (looksLikeRepoRoot(current)) return current;
|
|
205
|
+
const parent = path.dirname(current);
|
|
206
|
+
if (parent === current) return null;
|
|
207
|
+
current = parent;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function resolveRepos(config?: AriaConfig, explicit?: string[], fallbackRepoPath?: string): string[] {
|
|
212
|
+
if (explicit?.length) return explicit.map((repo) => path.resolve(repo)).filter(looksLikeRepoRoot);
|
|
213
|
+
|
|
214
|
+
const envRepos = process.env.ARIA_REPO_GUARD_REPOS?.split(path.delimiter)
|
|
215
|
+
.map((repo) => repo.trim())
|
|
216
|
+
.filter(Boolean) || [];
|
|
217
|
+
if (envRepos.length) return envRepos.map((repo) => path.resolve(repo)).filter(looksLikeRepoRoot);
|
|
218
|
+
|
|
219
|
+
const loaded = config || loadConfig();
|
|
220
|
+
const configured = (loaded.repositories || [])
|
|
221
|
+
.map((repo) => path.resolve(repo.path))
|
|
222
|
+
.filter(looksLikeRepoRoot);
|
|
223
|
+
if (configured.length) return configured;
|
|
224
|
+
|
|
225
|
+
if (fallbackRepoPath) {
|
|
226
|
+
const resolvedFallback = findRepoRoot(fallbackRepoPath);
|
|
227
|
+
if (resolvedFallback) return [resolvedFallback];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const cwd = findRepoRoot(process.cwd());
|
|
231
|
+
return cwd ? [cwd] : [];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function isDoctrineBound(relPath: string): boolean {
|
|
235
|
+
const normalized = normalizeRel(relPath);
|
|
236
|
+
if (!DOCTRINE_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return false;
|
|
237
|
+
if (!DOCTRINE_EXTENSIONS.has(path.extname(normalized))) return false;
|
|
238
|
+
const parts = normalized.split('/');
|
|
239
|
+
if (parts.some((part) => IGNORED_SEGMENTS.has(part))) return false;
|
|
240
|
+
if (normalized.endsWith('.map')) return false;
|
|
241
|
+
if (/package-lock\.json$|pnpm-lock\.yaml$|yarn\.lock$/i.test(normalized)) return false;
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function walkDoctrineFiles(repoRoot: string): string[] {
|
|
246
|
+
const found: string[] = [];
|
|
247
|
+
|
|
248
|
+
function visit(absPath: string): void {
|
|
249
|
+
const stat = statSync(absPath);
|
|
250
|
+
if (stat.isDirectory()) {
|
|
251
|
+
const base = path.basename(absPath);
|
|
252
|
+
if (IGNORED_SEGMENTS.has(base)) return;
|
|
253
|
+
for (const child of readdirSync(absPath)) {
|
|
254
|
+
visit(path.join(absPath, child));
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const rel = path.relative(repoRoot, absPath);
|
|
260
|
+
if (isDoctrineBound(rel)) found.push(absPath);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const prefix of DOCTRINE_PREFIXES) {
|
|
264
|
+
const abs = path.join(repoRoot, prefix.slice(0, -1));
|
|
265
|
+
if (existsSync(abs)) visit(abs);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return found;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function gateFile(repoRoot: string, relPath: string): { ok: true } | { ok: false; detail: string } {
|
|
272
|
+
const hookPath = path.join(homedir(), '.claude', 'hooks', 'aria-repo-doctrine-gate.mjs');
|
|
273
|
+
try {
|
|
274
|
+
execFileSync('node', [hookPath, '--repo', repoRoot, '--paths', relPath], {
|
|
275
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
276
|
+
encoding: 'utf8',
|
|
277
|
+
});
|
|
278
|
+
return { ok: true };
|
|
279
|
+
} catch (error) {
|
|
280
|
+
const detail = error instanceof Error && 'stderr' in error
|
|
281
|
+
? String((error as { stderr?: string }).stderr || (error as { stdout?: string }).stdout || error.message)
|
|
282
|
+
: String(error);
|
|
283
|
+
return { ok: false, detail: detail.trim() || 'aria repo doctrine gate rejected the file' };
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function writeShadow(repoRoot: string, relPath: string, content: string): void {
|
|
288
|
+
const dst = shadowPath(repoRoot, relPath);
|
|
289
|
+
ensureParent(dst);
|
|
290
|
+
writeFileSync(dst, content, { mode: 0o600 });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function readShadowContent(repoRoot: string, relPath: string): string | null {
|
|
294
|
+
const shadow = shadowPath(repoRoot, relPath);
|
|
295
|
+
if (!existsSync(shadow)) return null;
|
|
296
|
+
try {
|
|
297
|
+
return readFileSync(shadow, 'utf8');
|
|
298
|
+
} catch {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function readTrackedContent(repoRoot: string, relPath: string): string | null {
|
|
304
|
+
try {
|
|
305
|
+
return execFileSync('git', ['-C', repoRoot, 'show', `HEAD:${normalizeRel(relPath)}`], {
|
|
306
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
307
|
+
encoding: 'utf8',
|
|
308
|
+
});
|
|
309
|
+
} catch {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function recoveryContent(repoRoot: string, relPath: string): string | null {
|
|
315
|
+
return readShadowContent(repoRoot, relPath) ?? readTrackedContent(repoRoot, relPath);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function restorePriorContent(repoRoot: string, absPath: string, relPath: string): 'restored' | 'moved' | 'none' {
|
|
319
|
+
const prior = recoveryContent(repoRoot, relPath);
|
|
320
|
+
if (prior !== null) {
|
|
321
|
+
ensureParent(absPath);
|
|
322
|
+
writeFileSync(absPath, prior, { mode: 0o644 });
|
|
323
|
+
writeShadow(repoRoot, relPath, prior);
|
|
324
|
+
return 'restored';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (!existsSync(absPath)) return 'none';
|
|
328
|
+
|
|
329
|
+
const movedDst = quarantinePath(repoRoot, relPath);
|
|
330
|
+
ensureParent(movedDst);
|
|
331
|
+
renameSync(absPath, movedDst);
|
|
332
|
+
return 'moved';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function initializeBaseline(repoRoot: string): void {
|
|
336
|
+
for (const absPath of walkDoctrineFiles(repoRoot)) {
|
|
337
|
+
const relPath = path.relative(repoRoot, absPath);
|
|
338
|
+
try {
|
|
339
|
+
writeShadow(repoRoot, relPath, readFileSync(absPath, 'utf8'));
|
|
340
|
+
} catch {}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function handleViolation(repoRoot: string, absPath: string, relPath: string, detail: string): void {
|
|
345
|
+
const fileContent = existsSync(absPath) ? readFileSync(absPath, 'utf8') : '';
|
|
346
|
+
const quarantineDst = quarantinePath(repoRoot, relPath);
|
|
347
|
+
ensureParent(quarantineDst);
|
|
348
|
+
writeFileSync(quarantineDst, fileContent, { mode: 0o600 });
|
|
349
|
+
|
|
350
|
+
const action = BLOCKING_MODE ? restorePriorContent(repoRoot, absPath, relPath) : 'none';
|
|
351
|
+
|
|
352
|
+
const summary = `${repoRoot}:${relPath}`;
|
|
353
|
+
writeState({ status: 'violation', lastViolation: summary, lastEvent: summary, lastError: detail });
|
|
354
|
+
appendEvent({
|
|
355
|
+
type: 'violation',
|
|
356
|
+
repoRoot,
|
|
357
|
+
relPath,
|
|
358
|
+
quarantineDst,
|
|
359
|
+
blockingMode: BLOCKING_MODE,
|
|
360
|
+
action,
|
|
361
|
+
detail,
|
|
362
|
+
});
|
|
363
|
+
emitAlert({
|
|
364
|
+
type: 'violation',
|
|
365
|
+
title: 'Aria Repo Guard blocked a doctrine violation',
|
|
366
|
+
message: BLOCKING_MODE
|
|
367
|
+
? 'The changed file was quarantined and the last known good content was restored.'
|
|
368
|
+
: 'A doctrine violation was detected while repo guard was in non-blocking mode.',
|
|
369
|
+
repoRoot,
|
|
370
|
+
relPath,
|
|
371
|
+
action,
|
|
372
|
+
detail,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function handleDeletedDoctrineFile(repoRoot: string, absPath: string, relPath: string): void {
|
|
377
|
+
const action = BLOCKING_MODE ? restorePriorContent(repoRoot, absPath, relPath) : 'none';
|
|
378
|
+
if (action === 'none') return;
|
|
379
|
+
const summary = `${repoRoot}:${relPath}`;
|
|
380
|
+
writeState({ status: 'violation', lastViolation: summary, lastEvent: summary, lastError: 'Doctrine-bound deletion blocked.' });
|
|
381
|
+
appendEvent({
|
|
382
|
+
type: 'blocked-delete',
|
|
383
|
+
repoRoot,
|
|
384
|
+
relPath,
|
|
385
|
+
blockingMode: BLOCKING_MODE,
|
|
386
|
+
action,
|
|
387
|
+
});
|
|
388
|
+
emitAlert({
|
|
389
|
+
type: 'blocked-delete',
|
|
390
|
+
title: 'Aria Repo Guard blocked a doctrine-bound deletion',
|
|
391
|
+
message: 'A doctrine-bound file deletion was intercepted and restored.',
|
|
392
|
+
repoRoot,
|
|
393
|
+
relPath,
|
|
394
|
+
action,
|
|
395
|
+
detail: 'Doctrine-bound deletion blocked.',
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function shouldIgnoreWatch(absPath: string): boolean {
|
|
400
|
+
const parts = normalizeRel(absPath).split('/');
|
|
401
|
+
return parts.some((part) => IGNORED_SEGMENTS.has(part));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function findOwningRepo(repos: string[], absPath: string): string | null {
|
|
405
|
+
const resolved = path.resolve(absPath);
|
|
406
|
+
for (const repo of repos) {
|
|
407
|
+
if (resolved === repo || resolved.startsWith(`${repo}${path.sep}`)) return repo;
|
|
408
|
+
}
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function processChangedFile(repoRoot: string, absPath: string): void {
|
|
413
|
+
const relPath = path.relative(repoRoot, absPath);
|
|
414
|
+
if (!isDoctrineBound(relPath)) return;
|
|
415
|
+
if (!existsSync(absPath)) {
|
|
416
|
+
handleDeletedDoctrineFile(repoRoot, absPath, relPath);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (statSync(absPath).isDirectory()) return;
|
|
420
|
+
|
|
421
|
+
const gate = gateFile(repoRoot, relPath);
|
|
422
|
+
if (gate.ok) {
|
|
423
|
+
writeShadow(repoRoot, relPath, readFileSync(absPath, 'utf8'));
|
|
424
|
+
const summary = `${repoRoot}:${relPath}`;
|
|
425
|
+
writeState({ status: 'watching', lastEvent: summary, lastError: null });
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
handleViolation(repoRoot, absPath, relPath, gate.detail);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export async function startRepoGuardDaemon(options: RepoGuardOptions = {}): Promise<void> {
|
|
433
|
+
const repos = resolveRepos(undefined, options.repos);
|
|
434
|
+
if (!repos.length) {
|
|
435
|
+
writeState({ status: 'error', watchedRepos: [], lastError: 'No git repositories configured for repo guard.' });
|
|
436
|
+
throw new Error('No git repositories configured for repo guard.');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
mkdirSync(SHADOW_ROOT, { recursive: true, mode: 0o700 });
|
|
440
|
+
mkdirSync(QUARANTINE_ROOT, { recursive: true, mode: 0o700 });
|
|
441
|
+
for (const repo of repos) initializeBaseline(repo);
|
|
442
|
+
|
|
443
|
+
writeState({ status: 'watching', watchedRepos: repos, lastError: null });
|
|
444
|
+
emitAlert({
|
|
445
|
+
type: 'status',
|
|
446
|
+
title: 'Aria Repo Guard watching repositories',
|
|
447
|
+
message: `Watching ${repos.length} repo(s) for doctrine-bound changes.`,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const pending = new Map<string, NodeJS.Timeout>();
|
|
451
|
+
const restoring = new Map<string, number>();
|
|
452
|
+
const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
453
|
+
|
|
454
|
+
const watcher = chokidar.watch(repos, {
|
|
455
|
+
ignoreInitial: true,
|
|
456
|
+
ignored: shouldIgnoreWatch,
|
|
457
|
+
awaitWriteFinish: {
|
|
458
|
+
stabilityThreshold: Math.max(200, debounceMs),
|
|
459
|
+
pollInterval: 50,
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
function schedule(absPath: string): void {
|
|
464
|
+
const repoRoot = findOwningRepo(repos, absPath);
|
|
465
|
+
if (!repoRoot) return;
|
|
466
|
+
|
|
467
|
+
const prior = pending.get(absPath);
|
|
468
|
+
if (prior) clearTimeout(prior);
|
|
469
|
+
|
|
470
|
+
const timer = setTimeout(() => {
|
|
471
|
+
pending.delete(absPath);
|
|
472
|
+
const restoredAt = restoring.get(absPath);
|
|
473
|
+
if (restoredAt && Date.now() - restoredAt < 1500) {
|
|
474
|
+
restoring.delete(absPath);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const relPath = path.relative(repoRoot, absPath);
|
|
479
|
+
if (!isDoctrineBound(relPath)) return;
|
|
480
|
+
if (!existsSync(absPath)) {
|
|
481
|
+
handleDeletedDoctrineFile(repoRoot, absPath, relPath);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const gate = gateFile(repoRoot, relPath);
|
|
486
|
+
if (gate.ok) {
|
|
487
|
+
if (!statSync(absPath).isDirectory()) {
|
|
488
|
+
writeShadow(repoRoot, relPath, readFileSync(absPath, 'utf8'));
|
|
489
|
+
}
|
|
490
|
+
writeState({ status: 'watching', lastEvent: `${repoRoot}:${relPath}`, lastError: null });
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
restoring.set(absPath, Date.now());
|
|
495
|
+
handleViolation(repoRoot, absPath, relPath, gate.detail);
|
|
496
|
+
}, debounceMs);
|
|
497
|
+
|
|
498
|
+
pending.set(absPath, timer);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
watcher
|
|
502
|
+
.on('add', schedule)
|
|
503
|
+
.on('change', schedule)
|
|
504
|
+
.on('unlink', schedule)
|
|
505
|
+
.on('error', (error) => {
|
|
506
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
507
|
+
writeState({ status: 'error', lastError: detail });
|
|
508
|
+
appendEvent({ type: 'watcher-error', detail });
|
|
509
|
+
emitAlert({
|
|
510
|
+
type: 'watcher-error',
|
|
511
|
+
title: 'Aria Repo Guard watcher error',
|
|
512
|
+
message: 'The repo guard watcher hit an error and needs attention.',
|
|
513
|
+
detail,
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function writeRepoGuardEnv(repos: string[]): string {
|
|
519
|
+
mkdirSync(ARIA_DIR, { recursive: true, mode: 0o700 });
|
|
520
|
+
writeFileSync(
|
|
521
|
+
ENV_PATH,
|
|
522
|
+
[
|
|
523
|
+
`ARIA_REPO_GUARD_REPOS=${repos.join(path.delimiter)}`,
|
|
524
|
+
`ARIA_REPO_GUARD_DEBOUNCE_MS=${String(DEFAULT_DEBOUNCE_MS)}`,
|
|
525
|
+
'ARIA_REPO_GUARD_BLOCKING=true',
|
|
526
|
+
'',
|
|
527
|
+
].join('\n'),
|
|
528
|
+
{ mode: 0o600 },
|
|
529
|
+
);
|
|
530
|
+
return ENV_PATH;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function installSystemdUserService(logs: string[]): void {
|
|
534
|
+
const systemctlPath = '/bin/systemctl';
|
|
535
|
+
if (process.platform !== 'linux' || !existsSync(systemctlPath)) return;
|
|
536
|
+
|
|
537
|
+
const serviceDir = path.join(homedir(), '.config', 'systemd', 'user');
|
|
538
|
+
const servicePath = path.join(serviceDir, 'aria-repo-guard.service');
|
|
539
|
+
mkdirSync(serviceDir, { recursive: true, mode: 0o755 });
|
|
540
|
+
|
|
541
|
+
const unit = [
|
|
542
|
+
'[Unit]',
|
|
543
|
+
'Description=Aria Repo Doctrine Guard',
|
|
544
|
+
'After=network.target',
|
|
545
|
+
'',
|
|
546
|
+
'[Service]',
|
|
547
|
+
'Type=simple',
|
|
548
|
+
`EnvironmentFile=-${ENV_PATH}`,
|
|
549
|
+
`ExecStart=${path.join(BIN_DIR, 'aria-repo-guard')} start`,
|
|
550
|
+
'Restart=always',
|
|
551
|
+
'RestartSec=2',
|
|
552
|
+
'',
|
|
553
|
+
'[Install]',
|
|
554
|
+
'WantedBy=default.target',
|
|
555
|
+
'',
|
|
556
|
+
].join('\n');
|
|
557
|
+
|
|
558
|
+
writeFileSync(servicePath, unit, { mode: 0o644 });
|
|
559
|
+
logs.push(`Installed systemd user unit → ${servicePath}`);
|
|
560
|
+
|
|
561
|
+
try {
|
|
562
|
+
execFileSync(systemctlPath, ['--user', 'daemon-reload'], { stdio: 'ignore' });
|
|
563
|
+
execFileSync(systemctlPath, ['--user', 'enable', '--now', 'aria-repo-guard.service'], { stdio: 'ignore' });
|
|
564
|
+
logs.push('Enabled and started systemd user service: aria-repo-guard.service');
|
|
565
|
+
} catch (error) {
|
|
566
|
+
logs.push(`⚠ repo guard service installed but not activated: ${error instanceof Error ? error.message : String(error)}`);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export async function installRepoGuardDaemon(config?: AriaConfig, fallbackRepoPath?: string): Promise<string[]> {
|
|
571
|
+
const repos = resolveRepos(config, undefined, fallbackRepoPath);
|
|
572
|
+
const logs: string[] = [];
|
|
573
|
+
if (!repos.length) {
|
|
574
|
+
logs.push('Skipped repo guard install: no git repositories configured.');
|
|
575
|
+
return logs;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
mkdirSync(BIN_DIR, { recursive: true, mode: 0o755 });
|
|
579
|
+
mkdirSync(SHADOW_ROOT, { recursive: true, mode: 0o700 });
|
|
580
|
+
mkdirSync(QUARANTINE_ROOT, { recursive: true, mode: 0o700 });
|
|
581
|
+
const envPath = writeRepoGuardEnv(repos);
|
|
582
|
+
writeWrapper(path.join(BIN_DIR, 'aria-repo-guard'), ['repo-guard']);
|
|
583
|
+
installSystemdUserService(logs);
|
|
584
|
+
|
|
585
|
+
logs.push(`Installed repo guard wrapper → ${path.join(BIN_DIR, 'aria-repo-guard')}`);
|
|
586
|
+
logs.push(`Installed repo guard env → ${envPath}`);
|
|
587
|
+
logs.push(`Repo guard watching ${repos.length} repos`);
|
|
588
|
+
return logs;
|
|
589
|
+
}
|