@rigstate/cli 0.6.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/.env.example +5 -0
- package/IMPLEMENTATION.md +239 -0
- package/QUICK_START.md +220 -0
- package/README.md +150 -0
- package/dist/index.cjs +3987 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3964 -0
- package/dist/index.js.map +1 -0
- package/install.sh +15 -0
- package/package.json +53 -0
- package/src/commands/check.ts +329 -0
- package/src/commands/config.ts +81 -0
- package/src/commands/daemon.ts +197 -0
- package/src/commands/env.ts +158 -0
- package/src/commands/fix.ts +140 -0
- package/src/commands/focus.ts +134 -0
- package/src/commands/hooks.ts +163 -0
- package/src/commands/init.ts +282 -0
- package/src/commands/link.ts +45 -0
- package/src/commands/login.ts +35 -0
- package/src/commands/mcp.ts +73 -0
- package/src/commands/nexus.ts +81 -0
- package/src/commands/override.ts +65 -0
- package/src/commands/scan.ts +242 -0
- package/src/commands/sync-rules.ts +191 -0
- package/src/commands/sync.ts +339 -0
- package/src/commands/watch.ts +283 -0
- package/src/commands/work.ts +172 -0
- package/src/daemon/bridge-listener.ts +127 -0
- package/src/daemon/core.ts +184 -0
- package/src/daemon/factory.ts +45 -0
- package/src/daemon/file-watcher.ts +97 -0
- package/src/daemon/guardian-monitor.ts +133 -0
- package/src/daemon/heuristic-engine.ts +203 -0
- package/src/daemon/intervention-protocol.ts +128 -0
- package/src/daemon/telemetry.ts +23 -0
- package/src/daemon/types.ts +18 -0
- package/src/hive/gateway.ts +74 -0
- package/src/hive/protocol.ts +29 -0
- package/src/hive/scrubber.ts +72 -0
- package/src/index.ts +85 -0
- package/src/nexus/council.ts +103 -0
- package/src/nexus/dispatcher.ts +133 -0
- package/src/utils/config.ts +83 -0
- package/src/utils/files.ts +95 -0
- package/src/utils/governance.ts +128 -0
- package/src/utils/logger.ts +66 -0
- package/src/utils/manifest.ts +18 -0
- package/src/utils/rule-engine.ts +292 -0
- package/src/utils/skills-provisioner.ts +153 -0
- package/src/utils/version.ts +1 -0
- package/src/utils/watchdog.ts +215 -0
- package/tsconfig.json +29 -0
- package/tsup.config.ts +11 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3964 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// node_modules/tsup/assets/esm_shims.js
|
|
13
|
+
import path from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
var init_esm_shims = __esm({
|
|
16
|
+
"node_modules/tsup/assets/esm_shims.js"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// src/utils/config.ts
|
|
22
|
+
import Conf from "conf";
|
|
23
|
+
function getApiKey() {
|
|
24
|
+
const apiKey = config.get("apiKey");
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
'\u274C Not logged in. Please run "rigstate login <your-api-key>" first.'
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return apiKey;
|
|
31
|
+
}
|
|
32
|
+
function setApiKey(key) {
|
|
33
|
+
config.set("apiKey", key);
|
|
34
|
+
}
|
|
35
|
+
function getProjectId() {
|
|
36
|
+
return config.get("projectId");
|
|
37
|
+
}
|
|
38
|
+
function setProjectId(projectId) {
|
|
39
|
+
config.set("projectId", projectId);
|
|
40
|
+
}
|
|
41
|
+
function getApiUrl() {
|
|
42
|
+
if (process.env.RIGSTATE_API_URL) {
|
|
43
|
+
return process.env.RIGSTATE_API_URL;
|
|
44
|
+
}
|
|
45
|
+
const storedUrl = config.get("apiUrl");
|
|
46
|
+
if (storedUrl) {
|
|
47
|
+
return storedUrl;
|
|
48
|
+
}
|
|
49
|
+
return "https://app.rigstate.com";
|
|
50
|
+
}
|
|
51
|
+
var config;
|
|
52
|
+
var init_config = __esm({
|
|
53
|
+
"src/utils/config.ts"() {
|
|
54
|
+
"use strict";
|
|
55
|
+
init_esm_shims();
|
|
56
|
+
config = new Conf({
|
|
57
|
+
projectName: "rigstate-cli",
|
|
58
|
+
defaults: {
|
|
59
|
+
apiUrl: "http://localhost:3000"
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// src/utils/skills-provisioner.ts
|
|
66
|
+
var skills_provisioner_exports = {};
|
|
67
|
+
__export(skills_provisioner_exports, {
|
|
68
|
+
generateSkillsDiscoveryBlock: () => generateSkillsDiscoveryBlock,
|
|
69
|
+
jitProvisionSkill: () => jitProvisionSkill,
|
|
70
|
+
provisionSkills: () => provisionSkills
|
|
71
|
+
});
|
|
72
|
+
import axios3 from "axios";
|
|
73
|
+
import fs5 from "fs/promises";
|
|
74
|
+
import path6 from "path";
|
|
75
|
+
import chalk5 from "chalk";
|
|
76
|
+
async function provisionSkills(apiUrl, apiKey, projectId, rootDir) {
|
|
77
|
+
const skills = [];
|
|
78
|
+
try {
|
|
79
|
+
const response = await axios3.get(`${apiUrl}/api/v1/skills`, {
|
|
80
|
+
params: { project_id: projectId },
|
|
81
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
82
|
+
});
|
|
83
|
+
if (response.data.success && response.data.data) {
|
|
84
|
+
for (const dbSkill of response.data.data) {
|
|
85
|
+
skills.push({
|
|
86
|
+
name: dbSkill.name,
|
|
87
|
+
description: dbSkill.description,
|
|
88
|
+
specialist: dbSkill.specialist || "General",
|
|
89
|
+
version: dbSkill.version || "1.0.0",
|
|
90
|
+
governance: dbSkill.governance || "OPEN",
|
|
91
|
+
content: dbSkill.content
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch (e) {
|
|
96
|
+
const msg = e.response?.data?.error || e.message;
|
|
97
|
+
console.log(chalk5.dim(` (Skills API not available: ${msg}, using core library)`));
|
|
98
|
+
}
|
|
99
|
+
if (skills.length === 0) {
|
|
100
|
+
const { getRigstateStandardSkills } = await import("@rigstate/rules-engine");
|
|
101
|
+
const coreSkills = getRigstateStandardSkills();
|
|
102
|
+
skills.push(...coreSkills);
|
|
103
|
+
}
|
|
104
|
+
const skillsDir = path6.join(rootDir, ".agent", "skills");
|
|
105
|
+
await fs5.mkdir(skillsDir, { recursive: true });
|
|
106
|
+
for (const skill of skills) {
|
|
107
|
+
const skillDir = path6.join(skillsDir, skill.name);
|
|
108
|
+
await fs5.mkdir(skillDir, { recursive: true });
|
|
109
|
+
const skillContent = `---
|
|
110
|
+
name: ${skill.name}
|
|
111
|
+
description: ${skill.description}
|
|
112
|
+
version: "${skill.version}"
|
|
113
|
+
specialist: ${skill.specialist}
|
|
114
|
+
governance: ${skill.governance}
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
${skill.content}
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
*Provisioned by Rigstate CLI. Do not modify manually.*`;
|
|
121
|
+
const skillPath = path6.join(skillDir, "SKILL.md");
|
|
122
|
+
await fs5.writeFile(skillPath, skillContent, "utf-8");
|
|
123
|
+
}
|
|
124
|
+
console.log(chalk5.green(` \u2705 Provisioned ${skills.length} skill(s) to .agent/skills/`));
|
|
125
|
+
return skills;
|
|
126
|
+
}
|
|
127
|
+
function generateSkillsDiscoveryBlock(skills) {
|
|
128
|
+
if (skills.length === 0) return "";
|
|
129
|
+
const skillBlocks = skills.map((skill) => ` <skill>
|
|
130
|
+
<name>${skill.name}</name>
|
|
131
|
+
<description>${skill.description}</description>
|
|
132
|
+
<location>.agent/skills/${skill.name}/SKILL.md</location>
|
|
133
|
+
</skill>`).join("\n");
|
|
134
|
+
return `<available_skills>
|
|
135
|
+
${skillBlocks}
|
|
136
|
+
</available_skills>`;
|
|
137
|
+
}
|
|
138
|
+
async function jitProvisionSkill(skillId, apiUrl, apiKey, projectId, rootDir) {
|
|
139
|
+
const rulesPath = path6.join(rootDir, ".cursorrules");
|
|
140
|
+
let rulesContent = "";
|
|
141
|
+
try {
|
|
142
|
+
rulesContent = await fs5.readFile(rulesPath, "utf-8");
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const isProvisioned = rulesContent.includes(`<name>${skillId}</name>`) || rulesContent.includes(`.agent/skills/${skillId}`);
|
|
147
|
+
if (isProvisioned) return false;
|
|
148
|
+
console.log(chalk5.yellow(` \u26A1 JIT PROVISIONING: Injecting ${skillId}...`));
|
|
149
|
+
try {
|
|
150
|
+
const skills = await provisionSkills(apiUrl, apiKey, projectId, rootDir);
|
|
151
|
+
const skillsBlock = generateSkillsDiscoveryBlock(skills);
|
|
152
|
+
if (rulesContent.includes("<available_skills>")) {
|
|
153
|
+
rulesContent = rulesContent.replace(
|
|
154
|
+
/<available_skills>[\s\S]*?<\/available_skills>/,
|
|
155
|
+
skillsBlock
|
|
156
|
+
);
|
|
157
|
+
} else if (rulesContent.includes("## \u{1F9E0} PROJECT CONTEXT")) {
|
|
158
|
+
const insertPoint = rulesContent.indexOf("---", rulesContent.indexOf("## \u{1F9E0} PROJECT CONTEXT"));
|
|
159
|
+
if (insertPoint !== -1) {
|
|
160
|
+
rulesContent = rulesContent.slice(0, insertPoint + 3) + "\n\n" + skillsBlock + "\n" + rulesContent.slice(insertPoint + 3);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
await fs5.writeFile(rulesPath, rulesContent, "utf-8");
|
|
164
|
+
return true;
|
|
165
|
+
} catch (e) {
|
|
166
|
+
console.log(chalk5.red(` Failed to provision skill: ${e.message}`));
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
var init_skills_provisioner = __esm({
|
|
171
|
+
"src/utils/skills-provisioner.ts"() {
|
|
172
|
+
"use strict";
|
|
173
|
+
init_esm_shims();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// src/utils/governance.ts
|
|
178
|
+
var governance_exports = {};
|
|
179
|
+
__export(governance_exports, {
|
|
180
|
+
InterventionLevel: () => InterventionLevel,
|
|
181
|
+
clearSoftLock: () => clearSoftLock,
|
|
182
|
+
getGovernanceConfig: () => getGovernanceConfig,
|
|
183
|
+
getSessionState: () => getSessionState,
|
|
184
|
+
performOverride: () => performOverride,
|
|
185
|
+
setSoftLock: () => setSoftLock
|
|
186
|
+
});
|
|
187
|
+
import fs6 from "fs/promises";
|
|
188
|
+
import path7 from "path";
|
|
189
|
+
import chalk6 from "chalk";
|
|
190
|
+
async function getGovernanceConfig(rootDir = process.cwd()) {
|
|
191
|
+
try {
|
|
192
|
+
const configPath = path7.join(rootDir, "rigstate.config.json");
|
|
193
|
+
const content = await fs6.readFile(configPath, "utf-8");
|
|
194
|
+
const userConfig = JSON.parse(content);
|
|
195
|
+
return {
|
|
196
|
+
governance: {
|
|
197
|
+
...DEFAULT_CONFIG.governance,
|
|
198
|
+
...userConfig.governance
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
} catch (e) {
|
|
202
|
+
return DEFAULT_CONFIG;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function getSessionState(rootDir = process.cwd()) {
|
|
206
|
+
try {
|
|
207
|
+
const sessionPath = path7.join(rootDir, ".rigstate", "session.json");
|
|
208
|
+
const content = await fs6.readFile(sessionPath, "utf-8");
|
|
209
|
+
return JSON.parse(content);
|
|
210
|
+
} catch (e) {
|
|
211
|
+
return DEFAULT_SESSION;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async function setSoftLock(reason, violationId, rootDir = process.cwd()) {
|
|
215
|
+
const sessionPath = path7.join(rootDir, ".rigstate", "session.json");
|
|
216
|
+
const state = {
|
|
217
|
+
status: "SOFT_LOCK",
|
|
218
|
+
active_violation: violationId,
|
|
219
|
+
lock_reason: reason,
|
|
220
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
221
|
+
};
|
|
222
|
+
await fs6.mkdir(path7.dirname(sessionPath), { recursive: true });
|
|
223
|
+
await fs6.writeFile(sessionPath, JSON.stringify(state, null, 2), "utf-8");
|
|
224
|
+
}
|
|
225
|
+
async function clearSoftLock(rootDir = process.cwd()) {
|
|
226
|
+
const sessionPath = path7.join(rootDir, ".rigstate", "session.json");
|
|
227
|
+
const state = {
|
|
228
|
+
...DEFAULT_SESSION,
|
|
229
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
230
|
+
};
|
|
231
|
+
await fs6.mkdir(path7.dirname(sessionPath), { recursive: true });
|
|
232
|
+
await fs6.writeFile(sessionPath, JSON.stringify(state, null, 2), "utf-8");
|
|
233
|
+
}
|
|
234
|
+
async function performOverride(violationId, reason, rootDir = process.cwd()) {
|
|
235
|
+
const config2 = await getGovernanceConfig(rootDir);
|
|
236
|
+
if (!config2.governance.allow_overrides) {
|
|
237
|
+
console.log(chalk6.red("\u274C Overrides are disabled for this project."));
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
await clearSoftLock(rootDir);
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
var InterventionLevel, DEFAULT_CONFIG, DEFAULT_SESSION;
|
|
244
|
+
var init_governance = __esm({
|
|
245
|
+
"src/utils/governance.ts"() {
|
|
246
|
+
"use strict";
|
|
247
|
+
init_esm_shims();
|
|
248
|
+
InterventionLevel = /* @__PURE__ */ ((InterventionLevel2) => {
|
|
249
|
+
InterventionLevel2[InterventionLevel2["GHOST"] = 0] = "GHOST";
|
|
250
|
+
InterventionLevel2[InterventionLevel2["NUDGE"] = 1] = "NUDGE";
|
|
251
|
+
InterventionLevel2[InterventionLevel2["SENTINEL"] = 2] = "SENTINEL";
|
|
252
|
+
return InterventionLevel2;
|
|
253
|
+
})(InterventionLevel || {});
|
|
254
|
+
DEFAULT_CONFIG = {
|
|
255
|
+
governance: {
|
|
256
|
+
intervention_level: 0 /* GHOST */,
|
|
257
|
+
allow_overrides: true
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
DEFAULT_SESSION = {
|
|
261
|
+
status: "OPEN",
|
|
262
|
+
active_violation: null,
|
|
263
|
+
lock_reason: null,
|
|
264
|
+
last_updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// src/utils/watchdog.ts
|
|
270
|
+
var watchdog_exports = {};
|
|
271
|
+
__export(watchdog_exports, {
|
|
272
|
+
runGuardianWatchdog: () => runGuardianWatchdog
|
|
273
|
+
});
|
|
274
|
+
import fs7 from "fs/promises";
|
|
275
|
+
import path8 from "path";
|
|
276
|
+
import chalk7 from "chalk";
|
|
277
|
+
import axios4 from "axios";
|
|
278
|
+
async function countLines(filePath) {
|
|
279
|
+
try {
|
|
280
|
+
const content = await fs7.readFile(filePath, "utf-8");
|
|
281
|
+
return content.split("\n").length;
|
|
282
|
+
} catch (e) {
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async function getFiles(dir, extension) {
|
|
287
|
+
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
288
|
+
const files = await Promise.all(entries.map(async (entry) => {
|
|
289
|
+
const res = path8.resolve(dir, entry.name);
|
|
290
|
+
if (entry.isDirectory()) {
|
|
291
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".next" || entry.name === "dist") return [];
|
|
292
|
+
return getFiles(res, extension);
|
|
293
|
+
} else {
|
|
294
|
+
return extension.some((ext) => entry.name.endsWith(ext)) ? res : [];
|
|
295
|
+
}
|
|
296
|
+
}));
|
|
297
|
+
return files.flat();
|
|
298
|
+
}
|
|
299
|
+
async function fetchRulesFromApi(projectId) {
|
|
300
|
+
try {
|
|
301
|
+
const apiUrl = getApiUrl();
|
|
302
|
+
const apiKey = getApiKey();
|
|
303
|
+
const response = await axios4.get(`${apiUrl}/api/v1/guardian/rules`, {
|
|
304
|
+
params: { project_id: projectId },
|
|
305
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
306
|
+
timeout: 1e4
|
|
307
|
+
});
|
|
308
|
+
if (response.data.success && response.data.data.settings) {
|
|
309
|
+
return {
|
|
310
|
+
lmax: response.data.data.settings.lmax || DEFAULT_LMAX,
|
|
311
|
+
lmaxWarning: response.data.data.settings.lmax_warning || DEFAULT_LMAX_WARNING,
|
|
312
|
+
source: "API (Dynamic)"
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
} catch (error) {
|
|
316
|
+
try {
|
|
317
|
+
const cachePath = path8.join(process.cwd(), CACHE_FILE);
|
|
318
|
+
const content = await fs7.readFile(cachePath, "utf-8");
|
|
319
|
+
const cached = JSON.parse(content);
|
|
320
|
+
if (cached.settings) {
|
|
321
|
+
return {
|
|
322
|
+
lmax: cached.settings.lmax || DEFAULT_LMAX,
|
|
323
|
+
lmaxWarning: cached.settings.lmax_warning || DEFAULT_LMAX_WARNING,
|
|
324
|
+
source: "Cache (Fallback)"
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
lmax: DEFAULT_LMAX,
|
|
332
|
+
lmaxWarning: DEFAULT_LMAX_WARNING,
|
|
333
|
+
source: "Default (Hardcoded)"
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
async function runGuardianWatchdog(rootPath, settings = {}, projectId) {
|
|
337
|
+
console.log(chalk7.bold("\n\u{1F6E1}\uFE0F Active Guardian Watchdog Initiated..."));
|
|
338
|
+
let lmax = settings.lmax || DEFAULT_LMAX;
|
|
339
|
+
let lmaxWarning = settings.lmax_warning || DEFAULT_LMAX_WARNING;
|
|
340
|
+
let ruleSource = settings.lmax ? "Settings (Passed)" : "Default";
|
|
341
|
+
if (projectId) {
|
|
342
|
+
const apiRules = await fetchRulesFromApi(projectId);
|
|
343
|
+
lmax = apiRules.lmax;
|
|
344
|
+
lmaxWarning = apiRules.lmaxWarning;
|
|
345
|
+
ruleSource = apiRules.source;
|
|
346
|
+
}
|
|
347
|
+
console.log(chalk7.dim(`Governance Rules: L_max=${lmax}, L_max_warning=${lmaxWarning}, Source: ${ruleSource}`));
|
|
348
|
+
const targetExtensions = [".ts", ".tsx"];
|
|
349
|
+
let scanTarget = rootPath;
|
|
350
|
+
const webSrc = path8.join(rootPath, "apps", "web", "src");
|
|
351
|
+
try {
|
|
352
|
+
await fs7.access(webSrc);
|
|
353
|
+
scanTarget = webSrc;
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
console.log(chalk7.dim(`Scanning target: ${path8.relative(process.cwd(), scanTarget)}`));
|
|
357
|
+
const files = await getFiles(scanTarget, targetExtensions);
|
|
358
|
+
let violations = 0;
|
|
359
|
+
let warnings = 0;
|
|
360
|
+
const results = [];
|
|
361
|
+
for (const file of files) {
|
|
362
|
+
const lines = await countLines(file);
|
|
363
|
+
const relPath = path8.relative(rootPath, file);
|
|
364
|
+
if (lines > lmax) {
|
|
365
|
+
results.push({ file: relPath, lines, status: "VIOLATION" });
|
|
366
|
+
violations++;
|
|
367
|
+
console.log(chalk7.red(`[VIOLATION] ${relPath}: ${lines} lines (Limit: ${lmax})`));
|
|
368
|
+
} else if (lines > lmaxWarning) {
|
|
369
|
+
results.push({ file: relPath, lines, status: "WARNING" });
|
|
370
|
+
warnings++;
|
|
371
|
+
console.log(chalk7.yellow(`[WARNING] ${relPath}: ${lines} lines (Threshold: ${lmaxWarning})`));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (violations === 0 && warnings === 0) {
|
|
375
|
+
console.log(chalk7.green(`\u2714 All ${files.length} files are within governance limits.`));
|
|
376
|
+
} else {
|
|
377
|
+
console.log("\n" + chalk7.bold("Summary:"));
|
|
378
|
+
console.log(chalk7.red(`Violations: ${violations}`));
|
|
379
|
+
console.log(chalk7.yellow(`Warnings: ${warnings}`));
|
|
380
|
+
const { getGovernanceConfig: getGovernanceConfig2, setSoftLock: setSoftLock2, InterventionLevel: InterventionLevel2 } = await Promise.resolve().then(() => (init_governance(), governance_exports));
|
|
381
|
+
const { governance } = await getGovernanceConfig2(rootPath);
|
|
382
|
+
console.log(chalk7.dim(`Intervention Level: ${InterventionLevel2[governance.intervention_level] || "UNKNOWN"} (${governance.intervention_level})`));
|
|
383
|
+
if (violations > 0) {
|
|
384
|
+
console.log(chalk7.red.bold("\nCRITICAL: Governance violations detected. Immediate refactoring required."));
|
|
385
|
+
if (governance.intervention_level >= InterventionLevel2.SENTINEL) {
|
|
386
|
+
console.log(chalk7.red.bold("\u{1F6D1} SENTINEL MODE: Session SOFT_LOCKED until resolved."));
|
|
387
|
+
console.log(chalk7.red(' Run "rigstate override <id> --reason \\"...\\"" if this is an emergency.'));
|
|
388
|
+
await setSoftLock2("Sentinel Mode: Governance Violations Detected", "ARC-VIOLATION", rootPath);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (projectId) {
|
|
393
|
+
try {
|
|
394
|
+
const apiUrl = getApiUrl();
|
|
395
|
+
const apiKey = getApiKey();
|
|
396
|
+
const payloadViolations = results.filter((r) => r.status === "VIOLATION").map((v) => ({
|
|
397
|
+
uid: "V-" + Buffer.from(v.file).toString("base64").replace(/=/g, ""),
|
|
398
|
+
filePath: v.file,
|
|
399
|
+
lineCount: v.lines,
|
|
400
|
+
limitValue: lmax,
|
|
401
|
+
severity: "CRITICAL"
|
|
402
|
+
}));
|
|
403
|
+
await axios4.post(`${apiUrl}/api/v1/guardian/sync`, {
|
|
404
|
+
projectId,
|
|
405
|
+
violations: payloadViolations,
|
|
406
|
+
warnings
|
|
407
|
+
}, {
|
|
408
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
409
|
+
});
|
|
410
|
+
console.log(chalk7.dim("\u2714 Violations synced to Rigstate Cloud."));
|
|
411
|
+
} catch (e) {
|
|
412
|
+
console.log(chalk7.dim("\u26A0 Cloud sync skipped: " + (e.message || "Unknown")));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
var DEFAULT_LMAX, DEFAULT_LMAX_WARNING, CACHE_FILE;
|
|
417
|
+
var init_watchdog = __esm({
|
|
418
|
+
"src/utils/watchdog.ts"() {
|
|
419
|
+
"use strict";
|
|
420
|
+
init_esm_shims();
|
|
421
|
+
init_config();
|
|
422
|
+
DEFAULT_LMAX = 400;
|
|
423
|
+
DEFAULT_LMAX_WARNING = 350;
|
|
424
|
+
CACHE_FILE = ".rigstate/rules-cache.json";
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// src/index.ts
|
|
429
|
+
init_esm_shims();
|
|
430
|
+
import { Command as Command19 } from "commander";
|
|
431
|
+
import chalk27 from "chalk";
|
|
432
|
+
|
|
433
|
+
// src/commands/login.ts
|
|
434
|
+
init_esm_shims();
|
|
435
|
+
init_config();
|
|
436
|
+
import { Command } from "commander";
|
|
437
|
+
import chalk from "chalk";
|
|
438
|
+
function createLoginCommand() {
|
|
439
|
+
return new Command("login").description("Authenticate with your Rigstate API key").argument("<api-key>", "Your Rigstate API key (starts with sk_)").action(async (apiKey) => {
|
|
440
|
+
try {
|
|
441
|
+
if (!apiKey || !apiKey.startsWith("sk_rigstate_")) {
|
|
442
|
+
console.error(chalk.red("\u274C Invalid API key format"));
|
|
443
|
+
console.error(chalk.dim('API keys must start with "sk_rigstate_"'));
|
|
444
|
+
process.exit(1);
|
|
445
|
+
}
|
|
446
|
+
setApiKey(apiKey);
|
|
447
|
+
console.log(chalk.green("\u2705 Successfully logged in!"));
|
|
448
|
+
console.log(
|
|
449
|
+
chalk.dim(
|
|
450
|
+
`
|
|
451
|
+
Your API key has been securely stored. You can now use "rigstate scan" to audit your code.`
|
|
452
|
+
)
|
|
453
|
+
);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.error(
|
|
456
|
+
chalk.red("\u274C Login failed:"),
|
|
457
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
458
|
+
);
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/commands/link.ts
|
|
465
|
+
init_esm_shims();
|
|
466
|
+
import { Command as Command2 } from "commander";
|
|
467
|
+
import fs from "fs/promises";
|
|
468
|
+
import path2 from "path";
|
|
469
|
+
import chalk2 from "chalk";
|
|
470
|
+
import os from "os";
|
|
471
|
+
function createLinkCommand() {
|
|
472
|
+
return new Command2("link").description("Link current directory to a Rigstate project").argument("<projectId>", "Project ID to link").action(async (projectId) => {
|
|
473
|
+
try {
|
|
474
|
+
const globalPath = path2.join(os.homedir(), ".rigstate", "config.json");
|
|
475
|
+
const globalData = await fs.readFile(globalPath, "utf-8").catch(() => null);
|
|
476
|
+
if (globalData) {
|
|
477
|
+
const config2 = JSON.parse(globalData);
|
|
478
|
+
const cwd = process.cwd();
|
|
479
|
+
if (config2.overrides && config2.overrides[cwd]) {
|
|
480
|
+
const overrideId = config2.overrides[cwd];
|
|
481
|
+
if (overrideId !== projectId) {
|
|
482
|
+
console.warn(chalk2.yellow(`Global override detected. Enforcing project ID: ${overrideId}`));
|
|
483
|
+
projectId = overrideId;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
} catch (e) {
|
|
488
|
+
}
|
|
489
|
+
const manifestPath = path2.join(process.cwd(), ".rigstate");
|
|
490
|
+
const content = {
|
|
491
|
+
project_id: projectId,
|
|
492
|
+
api_url: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
|
|
493
|
+
linked_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
494
|
+
};
|
|
495
|
+
try {
|
|
496
|
+
await fs.writeFile(manifestPath, JSON.stringify(content, null, 2), "utf-8");
|
|
497
|
+
console.log(chalk2.green(`\u2714 Linked to project ID: ${projectId}`));
|
|
498
|
+
console.log(chalk2.dim(`Created local context manifest at .rigstate`));
|
|
499
|
+
} catch (error) {
|
|
500
|
+
console.error(chalk2.red(`Failed to link project: ${error.message}`));
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/commands/scan.ts
|
|
506
|
+
init_esm_shims();
|
|
507
|
+
init_config();
|
|
508
|
+
import { Command as Command3 } from "commander";
|
|
509
|
+
import chalk3 from "chalk";
|
|
510
|
+
import ora from "ora";
|
|
511
|
+
import axios from "axios";
|
|
512
|
+
import { glob } from "glob";
|
|
513
|
+
import fs3 from "fs/promises";
|
|
514
|
+
import path4 from "path";
|
|
515
|
+
|
|
516
|
+
// src/utils/files.ts
|
|
517
|
+
init_esm_shims();
|
|
518
|
+
import fs2 from "fs/promises";
|
|
519
|
+
import path3 from "path";
|
|
520
|
+
async function readGitignore(dir) {
|
|
521
|
+
const gitignorePath = path3.join(dir, ".gitignore");
|
|
522
|
+
try {
|
|
523
|
+
const content = await fs2.readFile(gitignorePath, "utf-8");
|
|
524
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
525
|
+
} catch (error) {
|
|
526
|
+
return [];
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function shouldIgnore(filePath, patterns) {
|
|
530
|
+
const relativePath = filePath.replace(/^\.\//, "");
|
|
531
|
+
const defaultIgnores = [
|
|
532
|
+
"node_modules",
|
|
533
|
+
".git",
|
|
534
|
+
"dist",
|
|
535
|
+
"build",
|
|
536
|
+
".next",
|
|
537
|
+
".turbo",
|
|
538
|
+
"coverage",
|
|
539
|
+
".env",
|
|
540
|
+
".env.local"
|
|
541
|
+
];
|
|
542
|
+
const allPatterns = [...defaultIgnores, ...patterns];
|
|
543
|
+
for (const pattern of allPatterns) {
|
|
544
|
+
if (pattern.endsWith("/")) {
|
|
545
|
+
const dir = pattern.slice(0, -1);
|
|
546
|
+
if (relativePath.includes(`${dir}/`) || relativePath === dir) {
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
} else if (pattern.includes("*")) {
|
|
550
|
+
const regex = new RegExp(
|
|
551
|
+
"^" + pattern.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
552
|
+
);
|
|
553
|
+
if (regex.test(relativePath)) {
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
if (relativePath.includes(pattern)) {
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
function isCodeFile(filePath) {
|
|
565
|
+
const codeExtensions = [
|
|
566
|
+
".js",
|
|
567
|
+
".jsx",
|
|
568
|
+
".ts",
|
|
569
|
+
".tsx",
|
|
570
|
+
".py",
|
|
571
|
+
".java",
|
|
572
|
+
".go",
|
|
573
|
+
".rb",
|
|
574
|
+
".php",
|
|
575
|
+
".c",
|
|
576
|
+
".cpp",
|
|
577
|
+
".h",
|
|
578
|
+
".cs",
|
|
579
|
+
".swift",
|
|
580
|
+
".kt",
|
|
581
|
+
".rs",
|
|
582
|
+
".vue",
|
|
583
|
+
".svelte"
|
|
584
|
+
];
|
|
585
|
+
const ext = path3.extname(filePath).toLowerCase();
|
|
586
|
+
return codeExtensions.includes(ext);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// src/commands/scan.ts
|
|
590
|
+
function createScanCommand() {
|
|
591
|
+
return new Command3("scan").description("Scan code files for security and quality issues").argument("[path]", "Directory or file to scan", ".").option("--json", "Output results as JSON").option("--project <id>", "Project ID to associate with this scan").action(async (targetPath, options) => {
|
|
592
|
+
const spinner = ora();
|
|
593
|
+
try {
|
|
594
|
+
const apiKey = getApiKey();
|
|
595
|
+
const apiUrl = getApiUrl();
|
|
596
|
+
const projectId = options.project || getProjectId();
|
|
597
|
+
if (!projectId) {
|
|
598
|
+
console.warn(
|
|
599
|
+
chalk3.yellow(
|
|
600
|
+
"\u26A0\uFE0F No project ID specified. Use --project <id> or set a default."
|
|
601
|
+
)
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
const scanPath = path4.resolve(process.cwd(), targetPath);
|
|
605
|
+
spinner.start(`Scanning ${chalk3.cyan(scanPath)}...`);
|
|
606
|
+
const gitignorePatterns = await readGitignore(scanPath);
|
|
607
|
+
const pattern = path4.join(scanPath, "**/*");
|
|
608
|
+
const allFiles = await glob(pattern, {
|
|
609
|
+
nodir: true,
|
|
610
|
+
dot: false,
|
|
611
|
+
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", "**/build/**"]
|
|
612
|
+
});
|
|
613
|
+
const codeFiles = allFiles.filter((file) => {
|
|
614
|
+
const relativePath = path4.relative(scanPath, file);
|
|
615
|
+
return isCodeFile(file) && !shouldIgnore(relativePath, gitignorePatterns);
|
|
616
|
+
});
|
|
617
|
+
if (codeFiles.length === 0) {
|
|
618
|
+
spinner.warn(chalk3.yellow("No code files found to scan."));
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
spinner.text = `Found ${codeFiles.length} files. Scanning...`;
|
|
622
|
+
const results = [];
|
|
623
|
+
let totalIssues = 0;
|
|
624
|
+
const severityCounts = {};
|
|
625
|
+
for (let i = 0; i < codeFiles.length; i++) {
|
|
626
|
+
const filePath = codeFiles[i];
|
|
627
|
+
const relativePath = path4.relative(scanPath, filePath);
|
|
628
|
+
spinner.text = `Scanning ${i + 1}/${codeFiles.length}: ${relativePath}`;
|
|
629
|
+
try {
|
|
630
|
+
const content = await fs3.readFile(filePath, "utf-8");
|
|
631
|
+
const response = await axios.post(
|
|
632
|
+
`${apiUrl}/api/v1/audit`,
|
|
633
|
+
{
|
|
634
|
+
content,
|
|
635
|
+
file_path: relativePath,
|
|
636
|
+
project_id: projectId
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
headers: {
|
|
640
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
641
|
+
"Content-Type": "application/json"
|
|
642
|
+
},
|
|
643
|
+
timeout: 6e4
|
|
644
|
+
// 1 minute per file
|
|
645
|
+
}
|
|
646
|
+
);
|
|
647
|
+
const vulnerabilities = response.data.vulnerabilities || [];
|
|
648
|
+
if (vulnerabilities.length > 0) {
|
|
649
|
+
results.push({
|
|
650
|
+
id: response.data.id || relativePath,
|
|
651
|
+
file_path: relativePath,
|
|
652
|
+
issues: vulnerabilities.map((v) => ({
|
|
653
|
+
type: v.type,
|
|
654
|
+
severity: v.severity,
|
|
655
|
+
message: v.description || v.title,
|
|
656
|
+
line: v.line_number
|
|
657
|
+
}))
|
|
658
|
+
});
|
|
659
|
+
totalIssues += vulnerabilities.length;
|
|
660
|
+
vulnerabilities.forEach((v) => {
|
|
661
|
+
severityCounts[v.severity] = (severityCounts[v.severity] || 0) + 1;
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
} catch (fileError) {
|
|
665
|
+
if (axios.isAxiosError(fileError)) {
|
|
666
|
+
console.warn(chalk3.yellow(`
|
|
667
|
+
\u26A0\uFE0F Skipping ${relativePath}: ${fileError.message}`));
|
|
668
|
+
} else {
|
|
669
|
+
console.warn(chalk3.yellow(`
|
|
670
|
+
\u26A0\uFE0F Error reading ${relativePath}`));
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
spinner.succeed(chalk3.green("\u2705 Scan completed!"));
|
|
675
|
+
const aggregatedResponse = {
|
|
676
|
+
results,
|
|
677
|
+
summary: {
|
|
678
|
+
total_files: codeFiles.length,
|
|
679
|
+
total_issues: totalIssues,
|
|
680
|
+
by_severity: severityCounts
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
if (options.json) {
|
|
684
|
+
console.log(JSON.stringify(aggregatedResponse, null, 2));
|
|
685
|
+
} else {
|
|
686
|
+
printPrettyResults(aggregatedResponse);
|
|
687
|
+
}
|
|
688
|
+
} catch (error) {
|
|
689
|
+
spinner.fail(chalk3.red("\u274C Scan failed"));
|
|
690
|
+
if (axios.isAxiosError(error)) {
|
|
691
|
+
if (error.response) {
|
|
692
|
+
console.error(chalk3.red("API Error:"), error.response.data);
|
|
693
|
+
} else if (error.request) {
|
|
694
|
+
console.error(
|
|
695
|
+
chalk3.red("Network Error:"),
|
|
696
|
+
"Could not reach the API. Is the server running?"
|
|
697
|
+
);
|
|
698
|
+
} else {
|
|
699
|
+
console.error(chalk3.red("Error:"), error.message);
|
|
700
|
+
}
|
|
701
|
+
} else {
|
|
702
|
+
console.error(
|
|
703
|
+
chalk3.red("Error:"),
|
|
704
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
process.exit(1);
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
function printPrettyResults(data) {
|
|
712
|
+
const { results, summary } = data;
|
|
713
|
+
console.log("\n" + chalk3.bold("\u{1F4CA} Scan Summary"));
|
|
714
|
+
console.log(chalk3.dim("\u2500".repeat(60)));
|
|
715
|
+
console.log(`Total Files Scanned: ${chalk3.cyan(summary.total_files)}`);
|
|
716
|
+
console.log(`Total Issues Found: ${chalk3.yellow(summary.total_issues)}`);
|
|
717
|
+
if (summary.by_severity) {
|
|
718
|
+
console.log("\nIssues by Severity:");
|
|
719
|
+
Object.entries(summary.by_severity).forEach(([severity, count]) => {
|
|
720
|
+
const color = getSeverityColor(severity);
|
|
721
|
+
console.log(` ${color(`${severity}:`)} ${count}`);
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
if (results && results.length > 0) {
|
|
725
|
+
console.log("\n" + chalk3.bold("\u{1F50D} Detailed Results"));
|
|
726
|
+
console.log(chalk3.dim("\u2500".repeat(60)));
|
|
727
|
+
results.forEach((result) => {
|
|
728
|
+
if (result.issues && result.issues.length > 0) {
|
|
729
|
+
console.log(`
|
|
730
|
+
${chalk3.bold(result.file_path)}`);
|
|
731
|
+
result.issues.forEach((issue) => {
|
|
732
|
+
const severityColor = getSeverityColor(issue.severity);
|
|
733
|
+
const lineInfo = issue.line ? chalk3.dim(`:${issue.line}`) : "";
|
|
734
|
+
console.log(
|
|
735
|
+
` ${severityColor(`[${issue.severity.toUpperCase()}]`)} ${issue.type}${lineInfo}`
|
|
736
|
+
);
|
|
737
|
+
console.log(` ${chalk3.dim(issue.message)}`);
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
console.log("\n" + chalk3.dim("\u2500".repeat(60)));
|
|
743
|
+
}
|
|
744
|
+
function getSeverityColor(severity) {
|
|
745
|
+
switch (severity.toLowerCase()) {
|
|
746
|
+
case "critical":
|
|
747
|
+
return chalk3.red.bold;
|
|
748
|
+
case "high":
|
|
749
|
+
return chalk3.red;
|
|
750
|
+
case "medium":
|
|
751
|
+
return chalk3.yellow;
|
|
752
|
+
case "low":
|
|
753
|
+
return chalk3.blue;
|
|
754
|
+
case "info":
|
|
755
|
+
return chalk3.gray;
|
|
756
|
+
default:
|
|
757
|
+
return chalk3.white;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/commands/fix.ts
|
|
762
|
+
init_esm_shims();
|
|
763
|
+
init_config();
|
|
764
|
+
import { Command as Command4 } from "commander";
|
|
765
|
+
import chalk4 from "chalk";
|
|
766
|
+
import ora2 from "ora";
|
|
767
|
+
import axios2 from "axios";
|
|
768
|
+
import { glob as glob2 } from "glob";
|
|
769
|
+
import fs4 from "fs/promises";
|
|
770
|
+
import path5 from "path";
|
|
771
|
+
import inquirer from "inquirer";
|
|
772
|
+
import * as Diff from "diff";
|
|
773
|
+
function createFixCommand() {
|
|
774
|
+
return new Command4("fix").description("Scan and interactively FIX detected issues using Rigstate AI").argument("[path]", "Directory or file to scan", ".").option("--project <id>", "Project ID to context-aware audit").action(async (targetPath, options) => {
|
|
775
|
+
const spinner = ora2();
|
|
776
|
+
try {
|
|
777
|
+
const apiKey = getApiKey();
|
|
778
|
+
const apiUrl = getApiUrl();
|
|
779
|
+
const projectId = options.project || getProjectId();
|
|
780
|
+
if (!projectId) {
|
|
781
|
+
console.log(chalk4.yellow("\u26A0\uFE0F Project ID is required for fixing. Using default or pass --project <id>"));
|
|
782
|
+
}
|
|
783
|
+
const scanPath = path5.resolve(process.cwd(), targetPath);
|
|
784
|
+
const gitignorePatterns = await readGitignore(scanPath);
|
|
785
|
+
const pattern = path5.join(scanPath, "**/*");
|
|
786
|
+
const allFiles = await glob2(pattern, { nodir: true, dot: false, ignore: ["**/node_modules/**", "**/.git/**"] });
|
|
787
|
+
const codeFiles = allFiles.filter((file) => {
|
|
788
|
+
const relativePath = path5.relative(scanPath, file);
|
|
789
|
+
return isCodeFile(file) && !shouldIgnore(relativePath, gitignorePatterns);
|
|
790
|
+
});
|
|
791
|
+
if (codeFiles.length === 0) {
|
|
792
|
+
console.log(chalk4.yellow("No code files found."));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
console.log(chalk4.bold(`
|
|
796
|
+
\u{1F9E0} Rigstate Fix Mode`));
|
|
797
|
+
console.log(chalk4.dim(`Scanning ${codeFiles.length} files with Project Context...
|
|
798
|
+
`));
|
|
799
|
+
let fixedCount = 0;
|
|
800
|
+
for (let i = 0; i < codeFiles.length; i++) {
|
|
801
|
+
const filePath = codeFiles[i];
|
|
802
|
+
const relativePath = path5.relative(scanPath, filePath);
|
|
803
|
+
spinner.start(`Analyzing ${relativePath}...`);
|
|
804
|
+
try {
|
|
805
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
806
|
+
const response = await axios2.post(
|
|
807
|
+
`${apiUrl}/api/v1/audit`,
|
|
808
|
+
{ content, file_path: relativePath, project_id: projectId },
|
|
809
|
+
{ headers: { "Authorization": `Bearer ${apiKey}` }, timeout: 12e4 }
|
|
810
|
+
);
|
|
811
|
+
const vulnerabilities = response.data.vulnerabilities || [];
|
|
812
|
+
const fixableIssues = vulnerabilities.filter((v) => v.fixed_content);
|
|
813
|
+
if (fixableIssues.length > 0) {
|
|
814
|
+
spinner.stop();
|
|
815
|
+
console.log(`
|
|
816
|
+
${chalk4.bold(relativePath)}: Found ${fixableIssues.length} fixable issues.`);
|
|
817
|
+
for (const issue of fixableIssues) {
|
|
818
|
+
console.log(chalk4.red(`
|
|
819
|
+
[${issue.type}] ${issue.title}`));
|
|
820
|
+
console.log(chalk4.dim(issue.suggestion || issue.message));
|
|
821
|
+
const diff = Diff.createTwoFilesPatch(relativePath, relativePath, content, issue.fixed_content, "Current", "Fixed");
|
|
822
|
+
console.log("\n" + diff.split("\n").slice(0, 15).join("\n") + (diff.split("\n").length > 15 ? "\n..." : ""));
|
|
823
|
+
const { apply } = await inquirer.prompt([{
|
|
824
|
+
type: "confirm",
|
|
825
|
+
name: "apply",
|
|
826
|
+
message: `Apply this fix to ${chalk4.cyan(relativePath)}?`,
|
|
827
|
+
default: true
|
|
828
|
+
}]);
|
|
829
|
+
if (apply) {
|
|
830
|
+
await fs4.writeFile(filePath, issue.fixed_content);
|
|
831
|
+
console.log(chalk4.green(`\u2705 Fixed applied!`));
|
|
832
|
+
fixedCount++;
|
|
833
|
+
if (issue.related_step_id) {
|
|
834
|
+
const { completeStep } = await inquirer.prompt([{
|
|
835
|
+
type: "confirm",
|
|
836
|
+
name: "completeStep",
|
|
837
|
+
message: `Frank thinks this fix completes a Roadmap Step. Mark as COMPLETED in Rigstate?`,
|
|
838
|
+
default: true
|
|
839
|
+
}]);
|
|
840
|
+
if (completeStep) {
|
|
841
|
+
try {
|
|
842
|
+
await axios2.post(
|
|
843
|
+
`${apiUrl}/api/v1/roadmap/update-status`,
|
|
844
|
+
{ step_id: issue.related_step_id, status: "COMPLETED", project_id: projectId },
|
|
845
|
+
{ headers: { "Authorization": `Bearer ${apiKey}` } }
|
|
846
|
+
);
|
|
847
|
+
console.log(chalk4.green(`\u{1F680} Roadmap updated! Mission Control is in sync.`));
|
|
848
|
+
} catch (err) {
|
|
849
|
+
console.error(chalk4.yellow(`Failed to update roadmap: ${err.message}`));
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
break;
|
|
854
|
+
} else {
|
|
855
|
+
console.log(chalk4.dim("Skipped."));
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
} else {
|
|
859
|
+
spinner.text = `Checked ${relativePath} (No auto-fixes)`;
|
|
860
|
+
}
|
|
861
|
+
} catch (e) {
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
spinner.stop();
|
|
865
|
+
console.log(chalk4.bold.green(`
|
|
866
|
+
|
|
867
|
+
\u{1F680} Fix session complete!`));
|
|
868
|
+
console.log(`Frank fixed ${fixedCount} detected issues.`);
|
|
869
|
+
console.log(chalk4.dim(`Run 'rigstate scan' to verify remaining issues.`));
|
|
870
|
+
} catch (error) {
|
|
871
|
+
spinner.fail("Fix session failed");
|
|
872
|
+
console.error(error.message);
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// src/commands/sync.ts
|
|
878
|
+
init_esm_shims();
|
|
879
|
+
init_config();
|
|
880
|
+
import { Command as Command5 } from "commander";
|
|
881
|
+
import chalk8 from "chalk";
|
|
882
|
+
import ora3 from "ora";
|
|
883
|
+
import axios5 from "axios";
|
|
884
|
+
import fs8 from "fs/promises";
|
|
885
|
+
import path9 from "path";
|
|
886
|
+
function createSyncCommand() {
|
|
887
|
+
const sync = new Command5("sync");
|
|
888
|
+
sync.description("Synchronize local state with Rigstate Cloud").option("-p, --project <id>", "Specify Project ID (saves to config automatically)").action(async (options) => {
|
|
889
|
+
const spinner = ora3("Synchronizing project state...").start();
|
|
890
|
+
try {
|
|
891
|
+
let apiKey;
|
|
892
|
+
try {
|
|
893
|
+
apiKey = getApiKey();
|
|
894
|
+
} catch (e) {
|
|
895
|
+
spinner.fail('Not authenticated. Run "rigstate login" first.');
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
let projectId = options.project;
|
|
899
|
+
if (!projectId) {
|
|
900
|
+
try {
|
|
901
|
+
const manifestPath = path9.join(process.cwd(), ".rigstate");
|
|
902
|
+
const manifestContent = await fs8.readFile(manifestPath, "utf-8");
|
|
903
|
+
const manifest = JSON.parse(manifestContent);
|
|
904
|
+
if (manifest.project_id) projectId = manifest.project_id;
|
|
905
|
+
} catch (e) {
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (!projectId) projectId = getProjectId();
|
|
909
|
+
if (options.project) {
|
|
910
|
+
setProjectId(options.project);
|
|
911
|
+
}
|
|
912
|
+
if (!projectId) {
|
|
913
|
+
spinner.fail("No project context found.\n Run with --project <id> once to save context.");
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const apiUrl = getApiUrl();
|
|
917
|
+
const response = await axios5.get(`${apiUrl}/api/v1/roadmap`, {
|
|
918
|
+
params: { project_id: projectId },
|
|
919
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
920
|
+
});
|
|
921
|
+
if (!response.data.success) {
|
|
922
|
+
throw new Error(response.data.error || "Unknown API failure");
|
|
923
|
+
}
|
|
924
|
+
const { roadmap, project } = response.data.data;
|
|
925
|
+
const timestamp = response.data.timestamp;
|
|
926
|
+
const targetPath = path9.join(process.cwd(), "roadmap.json");
|
|
927
|
+
const fileContent = JSON.stringify({
|
|
928
|
+
project,
|
|
929
|
+
last_synced: timestamp,
|
|
930
|
+
roadmap
|
|
931
|
+
}, null, 2);
|
|
932
|
+
await fs8.writeFile(targetPath, fileContent, "utf-8");
|
|
933
|
+
try {
|
|
934
|
+
const manifestPath = path9.join(process.cwd(), ".rigstate");
|
|
935
|
+
const manifestContent = {
|
|
936
|
+
project_id: projectId,
|
|
937
|
+
project_name: project,
|
|
938
|
+
last_synced: timestamp,
|
|
939
|
+
api_url: apiUrl
|
|
940
|
+
};
|
|
941
|
+
await fs8.writeFile(manifestPath, JSON.stringify(manifestContent, null, 2), "utf-8");
|
|
942
|
+
} catch (e) {
|
|
943
|
+
}
|
|
944
|
+
console.log(chalk8.bold("\n\u{1F9E0} Agent Skills Provisioning..."));
|
|
945
|
+
try {
|
|
946
|
+
const { provisionSkills: provisionSkills2, generateSkillsDiscoveryBlock: generateSkillsDiscoveryBlock2 } = await Promise.resolve().then(() => (init_skills_provisioner(), skills_provisioner_exports));
|
|
947
|
+
const skills = await provisionSkills2(apiUrl, apiKey, projectId, process.cwd());
|
|
948
|
+
const cursorRulesPath = path9.join(process.cwd(), ".cursorrules");
|
|
949
|
+
try {
|
|
950
|
+
let rulesContent = await fs8.readFile(cursorRulesPath, "utf-8");
|
|
951
|
+
const skillsBlock = generateSkillsDiscoveryBlock2(skills);
|
|
952
|
+
if (rulesContent.includes("<available_skills>")) {
|
|
953
|
+
rulesContent = rulesContent.replace(
|
|
954
|
+
/<available_skills>[\s\S]*?<\/available_skills>/,
|
|
955
|
+
skillsBlock
|
|
956
|
+
);
|
|
957
|
+
} else if (rulesContent.includes("## \u{1F9E0} PROJECT CONTEXT")) {
|
|
958
|
+
const insertPoint = rulesContent.indexOf("---", rulesContent.indexOf("## \u{1F9E0} PROJECT CONTEXT"));
|
|
959
|
+
if (insertPoint !== -1) {
|
|
960
|
+
rulesContent = rulesContent.slice(0, insertPoint + 3) + "\n\n" + skillsBlock + "\n" + rulesContent.slice(insertPoint + 3);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
await fs8.writeFile(cursorRulesPath, rulesContent, "utf-8");
|
|
964
|
+
console.log(chalk8.dim(` Updated .cursorrules with skills discovery block`));
|
|
965
|
+
} catch (e) {
|
|
966
|
+
}
|
|
967
|
+
} catch (e) {
|
|
968
|
+
console.log(chalk8.yellow(` \u26A0 Skills provisioning skipped: ${e.message}`));
|
|
969
|
+
}
|
|
970
|
+
try {
|
|
971
|
+
const logPath = path9.join(process.cwd(), ".rigstate", "logs", "last_execution.json");
|
|
972
|
+
try {
|
|
973
|
+
const logContent = await fs8.readFile(logPath, "utf-8");
|
|
974
|
+
const logData = JSON.parse(logContent);
|
|
975
|
+
if (logData.task_summary) {
|
|
976
|
+
await axios5.post(`${apiUrl}/api/v1/execution-logs`, {
|
|
977
|
+
project_id: projectId,
|
|
978
|
+
...logData,
|
|
979
|
+
agent_role: process.env.RIGSTATE_MODE === "SUPERVISOR" ? "SUPERVISOR" : "WORKER"
|
|
980
|
+
}, {
|
|
981
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
982
|
+
});
|
|
983
|
+
await fs8.unlink(logPath);
|
|
984
|
+
console.log(chalk8.dim(`\u2714 Mission Report uploaded.`));
|
|
985
|
+
}
|
|
986
|
+
} catch (e) {
|
|
987
|
+
if (e.code !== "ENOENT") {
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
} catch (e) {
|
|
991
|
+
}
|
|
992
|
+
spinner.succeed(chalk8.green(`Synced ${roadmap.length} roadmap steps for project "${project}"`));
|
|
993
|
+
console.log(chalk8.dim(`Local files updated: roadmap.json`));
|
|
994
|
+
const { runGuardianWatchdog: runGuardianWatchdog2 } = await Promise.resolve().then(() => (init_watchdog(), watchdog_exports));
|
|
995
|
+
const settings = response.data.data.settings || {};
|
|
996
|
+
await runGuardianWatchdog2(process.cwd(), settings, projectId);
|
|
997
|
+
console.log(chalk8.bold("\n\u{1F4E1} Agent Bridge Heartbeat..."));
|
|
998
|
+
try {
|
|
999
|
+
const bridgeResponse = await axios5.get(`${apiUrl}/api/v1/agent/bridge`, {
|
|
1000
|
+
params: { project_id: projectId },
|
|
1001
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1002
|
+
});
|
|
1003
|
+
if (bridgeResponse.data.success) {
|
|
1004
|
+
const tasks = bridgeResponse.data.tasks;
|
|
1005
|
+
const pending = tasks.filter((t) => t.status === "PENDING");
|
|
1006
|
+
const approved = tasks.filter((t) => t.status === "APPROVED");
|
|
1007
|
+
if (pending.length > 0 || approved.length > 0) {
|
|
1008
|
+
console.log(chalk8.yellow(`\u26A0 Bridge Alert: ${pending.length} pending, ${approved.length} approved tasks found.`));
|
|
1009
|
+
console.log(chalk8.dim('Run "rigstate fix" to process these tasks or ensure your IDE MCP server is active.'));
|
|
1010
|
+
} else {
|
|
1011
|
+
console.log(chalk8.green("\u2714 Heartbeat healthy. No pending bridge tasks."));
|
|
1012
|
+
}
|
|
1013
|
+
const pings = pending.filter((t) => t.proposal?.startsWith("ping"));
|
|
1014
|
+
for (const ping of pings) {
|
|
1015
|
+
await axios5.post(`${apiUrl}/api/v1/agent/bridge`, {
|
|
1016
|
+
bridge_id: ping.id,
|
|
1017
|
+
status: "COMPLETED",
|
|
1018
|
+
summary: "Pong! CLI Sync Heartbeat confirmed."
|
|
1019
|
+
}, {
|
|
1020
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1021
|
+
});
|
|
1022
|
+
console.log(chalk8.cyan(`\u{1F3D3} Pong! Acknowledged heartbeat signal [${ping.id}]`));
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
} catch (e) {
|
|
1026
|
+
console.log(chalk8.yellow(`\u26A0 Could not verify Bridge status: ${e.message}`));
|
|
1027
|
+
}
|
|
1028
|
+
if (options.project) {
|
|
1029
|
+
console.log(chalk8.blue(`Project context saved. Future commands will use this project.`));
|
|
1030
|
+
}
|
|
1031
|
+
try {
|
|
1032
|
+
const migrationDir = path9.join(process.cwd(), "supabase", "migrations");
|
|
1033
|
+
const files = await fs8.readdir(migrationDir);
|
|
1034
|
+
const sqlFiles = files.filter((f) => f.endsWith(".sql")).sort();
|
|
1035
|
+
if (sqlFiles.length > 0) {
|
|
1036
|
+
const latestMigration = sqlFiles[sqlFiles.length - 1];
|
|
1037
|
+
console.log(chalk8.dim(`
|
|
1038
|
+
\u{1F6E1} Migration Guard:`));
|
|
1039
|
+
console.log(chalk8.dim(` Latest Local: ${latestMigration}`));
|
|
1040
|
+
console.log(chalk8.yellow(` \u26A0 Ensure DB schema matches this version. CLI cannot verify Remote RLS policies directly.`));
|
|
1041
|
+
}
|
|
1042
|
+
} catch (e) {
|
|
1043
|
+
}
|
|
1044
|
+
try {
|
|
1045
|
+
const vaultResponse = await axios5.post(
|
|
1046
|
+
`${apiUrl}/api/v1/vault/sync`,
|
|
1047
|
+
{ project_id: projectId },
|
|
1048
|
+
{ headers: { Authorization: `Bearer ${apiKey}` } }
|
|
1049
|
+
);
|
|
1050
|
+
if (vaultResponse.data.success) {
|
|
1051
|
+
const vaultContent = vaultResponse.data.data.content || "";
|
|
1052
|
+
const localEnvPath = path9.join(process.cwd(), ".env.local");
|
|
1053
|
+
let localContent = "";
|
|
1054
|
+
try {
|
|
1055
|
+
localContent = await fs8.readFile(localEnvPath, "utf-8");
|
|
1056
|
+
} catch (e) {
|
|
1057
|
+
}
|
|
1058
|
+
if (vaultContent.trim() !== localContent.trim()) {
|
|
1059
|
+
console.log(chalk8.bold("\n\u{1F510} Sovereign Foundation (Vault):"));
|
|
1060
|
+
console.log(chalk8.yellow(" Status: Drift Detected / Update Available"));
|
|
1061
|
+
const { syncVault } = await import("inquirer").then((m) => m.default.prompt([{
|
|
1062
|
+
type: "confirm",
|
|
1063
|
+
name: "syncVault",
|
|
1064
|
+
message: "Synchronize local .env.local with Vault secrets?",
|
|
1065
|
+
default: false
|
|
1066
|
+
}]));
|
|
1067
|
+
if (syncVault) {
|
|
1068
|
+
await fs8.writeFile(localEnvPath, vaultContent, "utf-8");
|
|
1069
|
+
console.log(chalk8.green(" \u2705 .env.local synchronized with Vault."));
|
|
1070
|
+
} else {
|
|
1071
|
+
console.log(chalk8.dim(" Skipped vault sync."));
|
|
1072
|
+
}
|
|
1073
|
+
} else {
|
|
1074
|
+
console.log(chalk8.dim("\n\u{1F510} Sovereign Foundation: Synced."));
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
} catch (e) {
|
|
1078
|
+
}
|
|
1079
|
+
console.log(chalk8.dim("\n\u{1F6E1}\uFE0F System Integrity Check..."));
|
|
1080
|
+
await checkSystemIntegrity(apiUrl, apiKey, projectId);
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
if (axios5.isAxiosError(error)) {
|
|
1083
|
+
const message = error.response?.data?.error || error.message;
|
|
1084
|
+
spinner.fail(chalk8.red(`Sync failed: ${message}`));
|
|
1085
|
+
} else {
|
|
1086
|
+
spinner.fail(chalk8.red("Sync failed: " + (error.message || "Unknown error")));
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
return sync;
|
|
1091
|
+
}
|
|
1092
|
+
async function checkSystemIntegrity(apiUrl, apiKey, projectId) {
|
|
1093
|
+
try {
|
|
1094
|
+
const response = await axios5.get(`${apiUrl}/api/v1/system/integrity`, {
|
|
1095
|
+
params: { project_id: projectId },
|
|
1096
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1097
|
+
});
|
|
1098
|
+
if (response.data.success) {
|
|
1099
|
+
const { migrations, rls, guardian_violations } = response.data.data;
|
|
1100
|
+
if (migrations) {
|
|
1101
|
+
if (migrations.in_sync) {
|
|
1102
|
+
console.log(chalk8.green(` \u2705 Migrations synced (${migrations.count} versions)`));
|
|
1103
|
+
} else {
|
|
1104
|
+
console.log(chalk8.red(` \u{1F6D1} CRITICAL: DB Schema out of sync! ${migrations.missing?.length || 0} migrations not applied.`));
|
|
1105
|
+
if (migrations.missing?.length > 0) {
|
|
1106
|
+
console.log(chalk8.dim(` Missing: ${migrations.missing.slice(0, 3).join(", ")}${migrations.missing.length > 3 ? "..." : ""}`));
|
|
1107
|
+
}
|
|
1108
|
+
console.log(chalk8.yellow(` Run 'supabase db push' or apply migrations immediately.`));
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
if (rls) {
|
|
1112
|
+
if (rls.all_secured) {
|
|
1113
|
+
console.log(chalk8.green(` \u2705 RLS Audit Passed (${rls.table_count} tables secured)`));
|
|
1114
|
+
} else {
|
|
1115
|
+
console.log(chalk8.red(` \u{1F6D1} CRITICAL: Security Vulnerability! ${rls.unsecured?.length || 0} tables have RLS disabled.`));
|
|
1116
|
+
rls.unsecured?.forEach((table) => {
|
|
1117
|
+
console.log(chalk8.red(` - ${table}`));
|
|
1118
|
+
});
|
|
1119
|
+
console.log(chalk8.yellow(' Enable RLS immediately: ALTER TABLE "table" ENABLE ROW LEVEL SECURITY;'));
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
if (guardian_violations) {
|
|
1123
|
+
if (guardian_violations.count === 0) {
|
|
1124
|
+
console.log(chalk8.green(" \u2705 Guardian: No active violations"));
|
|
1125
|
+
} else {
|
|
1126
|
+
console.log(chalk8.yellow(` \u26A0\uFE0F Guardian: ${guardian_violations.count} active violations`));
|
|
1127
|
+
console.log(chalk8.dim(' Run "rigstate check" for details.'));
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
} catch (e) {
|
|
1132
|
+
console.log(chalk8.dim(" (System integrity check skipped - API endpoint not available)"));
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// src/commands/init.ts
|
|
1137
|
+
init_esm_shims();
|
|
1138
|
+
import { Command as Command6 } from "commander";
|
|
1139
|
+
import chalk9 from "chalk";
|
|
1140
|
+
import fs10 from "fs/promises";
|
|
1141
|
+
import path11 from "path";
|
|
1142
|
+
import ora4 from "ora";
|
|
1143
|
+
import { execSync } from "child_process";
|
|
1144
|
+
|
|
1145
|
+
// src/utils/manifest.ts
|
|
1146
|
+
init_esm_shims();
|
|
1147
|
+
import fs9 from "fs/promises";
|
|
1148
|
+
import path10 from "path";
|
|
1149
|
+
async function loadManifest() {
|
|
1150
|
+
try {
|
|
1151
|
+
const manifestPath = path10.join(process.cwd(), ".rigstate");
|
|
1152
|
+
const content = await fs9.readFile(manifestPath, "utf-8");
|
|
1153
|
+
return JSON.parse(content);
|
|
1154
|
+
} catch {
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// src/commands/init.ts
|
|
1160
|
+
init_config();
|
|
1161
|
+
import axios6 from "axios";
|
|
1162
|
+
function createInitCommand() {
|
|
1163
|
+
return new Command6("init").description("Initialize or link a Rigstate project (interactive mode available)").argument("[project-id]", "ID of the project to link (optional, prompts if not provided)").option("-f, --force", "Overwrite existing .cursorrules file").option("--rules-only", "Only regenerate .cursorrules without interactive setup").action(async (projectIdArg, options) => {
|
|
1164
|
+
const spinner = ora4("Initializing Rigstate project...").start();
|
|
1165
|
+
let apiKey;
|
|
1166
|
+
try {
|
|
1167
|
+
apiKey = getApiKey();
|
|
1168
|
+
} catch (e) {
|
|
1169
|
+
spinner.fail(chalk9.red('Not authenticated. Run "rigstate login" first.'));
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
const apiUrl = getApiUrl();
|
|
1173
|
+
let projectId = projectIdArg;
|
|
1174
|
+
try {
|
|
1175
|
+
if (options.rulesOnly) {
|
|
1176
|
+
const manifest = await loadManifest();
|
|
1177
|
+
if (!manifest) {
|
|
1178
|
+
spinner.fail('No .rigstate manifest found. Run "rigstate init" first.');
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
projectId = manifest.project_id;
|
|
1182
|
+
await generateRules(apiUrl, apiKey, projectId, options.force, spinner);
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
if (!projectId) {
|
|
1186
|
+
spinner.stop();
|
|
1187
|
+
const inquirer4 = (await import("inquirer")).default;
|
|
1188
|
+
spinner.start("Fetching your projects...");
|
|
1189
|
+
let projects = [];
|
|
1190
|
+
try {
|
|
1191
|
+
const projectsResponse = await axios6.get(`${apiUrl}/api/v1/projects`, {
|
|
1192
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1193
|
+
});
|
|
1194
|
+
if (projectsResponse.data.success) {
|
|
1195
|
+
projects = projectsResponse.data.data.projects || [];
|
|
1196
|
+
}
|
|
1197
|
+
} catch (e) {
|
|
1198
|
+
spinner.info("Projects API not available. Using manual entry mode.");
|
|
1199
|
+
}
|
|
1200
|
+
spinner.stop();
|
|
1201
|
+
if (projects.length === 0) {
|
|
1202
|
+
const { manualProjectId } = await inquirer4.prompt([
|
|
1203
|
+
{
|
|
1204
|
+
type: "input",
|
|
1205
|
+
name: "manualProjectId",
|
|
1206
|
+
message: "Enter Project ID (from Rigstate dashboard):",
|
|
1207
|
+
validate: (input) => input.trim() ? true : "Project ID is required"
|
|
1208
|
+
}
|
|
1209
|
+
]);
|
|
1210
|
+
projectId = manualProjectId;
|
|
1211
|
+
} else {
|
|
1212
|
+
const choices = [
|
|
1213
|
+
{ name: "\u2795 Create New Project", value: "NEW" },
|
|
1214
|
+
new inquirer4.Separator()
|
|
1215
|
+
];
|
|
1216
|
+
projects.forEach((p) => {
|
|
1217
|
+
choices.push({
|
|
1218
|
+
name: `[${p.organization_name || "Personal"}] ${p.name} (${p.status})`,
|
|
1219
|
+
value: p.id
|
|
1220
|
+
});
|
|
1221
|
+
});
|
|
1222
|
+
const { selectedId } = await inquirer4.prompt([
|
|
1223
|
+
{
|
|
1224
|
+
type: "list",
|
|
1225
|
+
name: "selectedId",
|
|
1226
|
+
message: "Select a project to link:",
|
|
1227
|
+
choices,
|
|
1228
|
+
pageSize: 15
|
|
1229
|
+
}
|
|
1230
|
+
]);
|
|
1231
|
+
if (selectedId === "NEW") {
|
|
1232
|
+
const { newName } = await inquirer4.prompt([
|
|
1233
|
+
{
|
|
1234
|
+
type: "input",
|
|
1235
|
+
name: "newName",
|
|
1236
|
+
message: "Enter project name:",
|
|
1237
|
+
validate: (input) => input.trim() ? true : "Name is required"
|
|
1238
|
+
}
|
|
1239
|
+
]);
|
|
1240
|
+
spinner.start("Fetching organizations...");
|
|
1241
|
+
let orgs = [];
|
|
1242
|
+
try {
|
|
1243
|
+
const orgsResponse = await axios6.get(`${apiUrl}/api/v1/organizations`, {
|
|
1244
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1245
|
+
});
|
|
1246
|
+
orgs = orgsResponse.data.data?.organizations || [];
|
|
1247
|
+
} catch (e) {
|
|
1248
|
+
}
|
|
1249
|
+
spinner.stop();
|
|
1250
|
+
let selectedOrgId = orgs[0]?.id;
|
|
1251
|
+
if (orgs.length > 1) {
|
|
1252
|
+
const { orgId } = await inquirer4.prompt([
|
|
1253
|
+
{
|
|
1254
|
+
type: "list",
|
|
1255
|
+
name: "orgId",
|
|
1256
|
+
message: "Which organization does this belong to?",
|
|
1257
|
+
choices: orgs.map((org) => ({
|
|
1258
|
+
name: `${org.name} (${org.role || "member"})`,
|
|
1259
|
+
value: org.id
|
|
1260
|
+
}))
|
|
1261
|
+
}
|
|
1262
|
+
]);
|
|
1263
|
+
selectedOrgId = orgId;
|
|
1264
|
+
}
|
|
1265
|
+
if (!selectedOrgId) {
|
|
1266
|
+
console.log(chalk9.yellow("No organization available. Please create the project via the Rigstate dashboard."));
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
spinner.start("Creating new project...");
|
|
1270
|
+
try {
|
|
1271
|
+
const createResponse = await axios6.post(`${apiUrl}/api/v1/projects`, {
|
|
1272
|
+
name: newName,
|
|
1273
|
+
organization_id: selectedOrgId
|
|
1274
|
+
}, {
|
|
1275
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1276
|
+
});
|
|
1277
|
+
if (!createResponse.data.success) {
|
|
1278
|
+
spinner.fail(chalk9.red("Failed to create project: " + createResponse.data.error));
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
projectId = createResponse.data.data.project.id;
|
|
1282
|
+
spinner.succeed(chalk9.green(`Created new project: ${newName}`));
|
|
1283
|
+
} catch (e) {
|
|
1284
|
+
spinner.fail(chalk9.red("Project creation API not available. Please create via dashboard."));
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
} else {
|
|
1288
|
+
projectId = selectedId;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
spinner.start(`Linking to project ID: ${projectId}...`);
|
|
1292
|
+
}
|
|
1293
|
+
setProjectId(projectId);
|
|
1294
|
+
const manifestPath = path11.join(process.cwd(), ".rigstate");
|
|
1295
|
+
const manifestContent = {
|
|
1296
|
+
project_id: projectId,
|
|
1297
|
+
last_linked: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1298
|
+
api_url: apiUrl
|
|
1299
|
+
};
|
|
1300
|
+
await fs10.writeFile(manifestPath, JSON.stringify(manifestContent, null, 2), "utf-8");
|
|
1301
|
+
try {
|
|
1302
|
+
await fs10.access(".git");
|
|
1303
|
+
} catch {
|
|
1304
|
+
spinner.text = "Initializing git repository...";
|
|
1305
|
+
execSync("git init", { stdio: "ignore" });
|
|
1306
|
+
}
|
|
1307
|
+
spinner.succeed(chalk9.green(`\u2705 Linked to project: ${projectId}`));
|
|
1308
|
+
await generateRules(apiUrl, apiKey, projectId, options.force, spinner);
|
|
1309
|
+
console.log("");
|
|
1310
|
+
console.log(chalk9.blue("Next steps:"));
|
|
1311
|
+
console.log(chalk9.dim(" rigstate sync - Sync roadmap and context"));
|
|
1312
|
+
console.log(chalk9.dim(" rigstate watch - Start development loop"));
|
|
1313
|
+
console.log(chalk9.dim(" rigstate focus - Get current task"));
|
|
1314
|
+
} catch (e) {
|
|
1315
|
+
spinner.fail(chalk9.red("Initialization failed: " + e.message));
|
|
1316
|
+
}
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
async function generateRules(apiUrl, apiKey, projectId, force, spinner) {
|
|
1320
|
+
spinner.start("Generating AI rules (MDC + AGENTS.md)...");
|
|
1321
|
+
try {
|
|
1322
|
+
const response = await axios6.post(`${apiUrl}/api/v1/rules/generate`, {
|
|
1323
|
+
project_id: projectId
|
|
1324
|
+
}, {
|
|
1325
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
1326
|
+
});
|
|
1327
|
+
if (response.data.success || response.data.files) {
|
|
1328
|
+
const files = response.data.files || [];
|
|
1329
|
+
if (files.length === 0 && response.data.rules) {
|
|
1330
|
+
const rulesPath = path11.join(process.cwd(), ".cursorrules");
|
|
1331
|
+
await fs10.writeFile(rulesPath, response.data.rules, "utf-8");
|
|
1332
|
+
spinner.succeed(chalk9.green("\u2714 Generated .cursorrules (legacy mode)"));
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
for (const file of files) {
|
|
1336
|
+
const targetPath = path11.join(process.cwd(), file.path);
|
|
1337
|
+
const targetDir = path11.dirname(targetPath);
|
|
1338
|
+
await fs10.mkdir(targetDir, { recursive: true });
|
|
1339
|
+
try {
|
|
1340
|
+
await fs10.access(targetPath);
|
|
1341
|
+
if (!force && !file.path.startsWith(".cursor/rules/")) {
|
|
1342
|
+
console.log(chalk9.dim(` ${file.path} already exists. Skipping.`));
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
} catch {
|
|
1346
|
+
}
|
|
1347
|
+
await fs10.writeFile(targetPath, file.content, "utf-8");
|
|
1348
|
+
}
|
|
1349
|
+
if (files.length > 0) {
|
|
1350
|
+
const legacyPath = path11.join(process.cwd(), ".cursorrules");
|
|
1351
|
+
try {
|
|
1352
|
+
const stats = await fs10.stat(legacyPath);
|
|
1353
|
+
if (stats.isFile()) {
|
|
1354
|
+
await fs10.rename(legacyPath, `${legacyPath}.bak`);
|
|
1355
|
+
console.log(chalk9.dim(" Moved legacy .cursorrules to .cursorrules.bak"));
|
|
1356
|
+
}
|
|
1357
|
+
} catch (e) {
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
spinner.succeed(chalk9.green(`\u2714 Generated ${files.length} rule files (v${response.data.version || "3.0"})`));
|
|
1361
|
+
} else {
|
|
1362
|
+
spinner.info(chalk9.dim(" Rules generation skipped (API response invalid)"));
|
|
1363
|
+
}
|
|
1364
|
+
} catch (e) {
|
|
1365
|
+
spinner.info(chalk9.dim(` Rules generation failed: ${e.message}`));
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// src/commands/check.ts
|
|
1370
|
+
init_esm_shims();
|
|
1371
|
+
init_config();
|
|
1372
|
+
import { Command as Command7 } from "commander";
|
|
1373
|
+
import chalk11 from "chalk";
|
|
1374
|
+
import ora5 from "ora";
|
|
1375
|
+
import axios7 from "axios";
|
|
1376
|
+
import { glob as glob3 } from "glob";
|
|
1377
|
+
import fs12 from "fs/promises";
|
|
1378
|
+
import path13 from "path";
|
|
1379
|
+
import { execSync as execSync2 } from "child_process";
|
|
1380
|
+
|
|
1381
|
+
// src/utils/rule-engine.ts
|
|
1382
|
+
init_esm_shims();
|
|
1383
|
+
import fs11 from "fs/promises";
|
|
1384
|
+
import path12 from "path";
|
|
1385
|
+
import chalk10 from "chalk";
|
|
1386
|
+
async function checkFile(filePath, rules, rootPath) {
|
|
1387
|
+
const violations = [];
|
|
1388
|
+
const relativePath = path12.relative(rootPath, filePath);
|
|
1389
|
+
try {
|
|
1390
|
+
const content = await fs11.readFile(filePath, "utf-8");
|
|
1391
|
+
const lines = content.split("\n");
|
|
1392
|
+
for (const rule of rules) {
|
|
1393
|
+
const ruleViolations = await evaluateRule(rule, content, lines, relativePath);
|
|
1394
|
+
violations.push(...ruleViolations);
|
|
1395
|
+
}
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
violations.push({
|
|
1398
|
+
file: relativePath,
|
|
1399
|
+
rule: "FILE_READ_ERROR",
|
|
1400
|
+
ruleType: "SYSTEM",
|
|
1401
|
+
severity: "warning",
|
|
1402
|
+
message: `Could not read file: ${error.message}`
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
return {
|
|
1406
|
+
file: relativePath,
|
|
1407
|
+
violations,
|
|
1408
|
+
passed: violations.length === 0
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
async function evaluateRule(rule, content, lines, filePath) {
|
|
1412
|
+
const violations = [];
|
|
1413
|
+
switch (rule.rule_type) {
|
|
1414
|
+
case "MAX_FILE_LINES": {
|
|
1415
|
+
const value = rule.value;
|
|
1416
|
+
const lineCount = lines.length;
|
|
1417
|
+
if (lineCount > value.limit) {
|
|
1418
|
+
violations.push({
|
|
1419
|
+
file: filePath,
|
|
1420
|
+
rule: rule.rule_name,
|
|
1421
|
+
ruleType: rule.rule_type,
|
|
1422
|
+
severity: "critical",
|
|
1423
|
+
message: `File exceeds ${value.limit} lines`,
|
|
1424
|
+
details: `Current: ${lineCount} lines (limit: ${value.limit})`
|
|
1425
|
+
});
|
|
1426
|
+
} else if (value.warning_threshold && lineCount > value.warning_threshold) {
|
|
1427
|
+
violations.push({
|
|
1428
|
+
file: filePath,
|
|
1429
|
+
rule: rule.rule_name,
|
|
1430
|
+
ruleType: rule.rule_type,
|
|
1431
|
+
severity: "warning",
|
|
1432
|
+
message: `File approaching line limit`,
|
|
1433
|
+
details: `Current: ${lineCount} lines (warning at: ${value.warning_threshold}, limit: ${value.limit})`
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
break;
|
|
1437
|
+
}
|
|
1438
|
+
case "MAX_FUNCTION_LINES": {
|
|
1439
|
+
const value = rule.value;
|
|
1440
|
+
const functionViolations = checkFunctionLines(content, lines, filePath, rule, value.limit);
|
|
1441
|
+
violations.push(...functionViolations);
|
|
1442
|
+
break;
|
|
1443
|
+
}
|
|
1444
|
+
case "PATTERN_FORBIDDEN": {
|
|
1445
|
+
const value = rule.value;
|
|
1446
|
+
const pattern = new RegExp(value.pattern, "g");
|
|
1447
|
+
let match;
|
|
1448
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
1449
|
+
const lineNumber = content.substring(0, match.index).split("\n").length;
|
|
1450
|
+
violations.push({
|
|
1451
|
+
file: filePath,
|
|
1452
|
+
rule: rule.rule_name,
|
|
1453
|
+
ruleType: rule.rule_type,
|
|
1454
|
+
severity: rule.severity,
|
|
1455
|
+
message: value.message || `Forbidden pattern found: ${value.pattern}`,
|
|
1456
|
+
line: lineNumber,
|
|
1457
|
+
details: `Found: "${match[0].substring(0, 50)}${match[0].length > 50 ? "..." : ""}"`
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
break;
|
|
1461
|
+
}
|
|
1462
|
+
case "PATTERN_REQUIRED": {
|
|
1463
|
+
const value = rule.value;
|
|
1464
|
+
const pattern = new RegExp(value.pattern);
|
|
1465
|
+
const shouldCheck = !value.context || filePath.includes(value.context);
|
|
1466
|
+
if (shouldCheck && !pattern.test(content)) {
|
|
1467
|
+
violations.push({
|
|
1468
|
+
file: filePath,
|
|
1469
|
+
rule: rule.rule_name,
|
|
1470
|
+
ruleType: rule.rule_type,
|
|
1471
|
+
severity: rule.severity,
|
|
1472
|
+
message: `Required pattern not found: ${value.pattern}`,
|
|
1473
|
+
details: value.context ? `Expected in files matching: ${value.context}` : void 0
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
break;
|
|
1477
|
+
}
|
|
1478
|
+
case "NAMING_CONVENTION": {
|
|
1479
|
+
const value = rule.value;
|
|
1480
|
+
const pattern = new RegExp(value.pattern);
|
|
1481
|
+
const fileName = path12.basename(filePath);
|
|
1482
|
+
if (filePath.includes(value.context) && !pattern.test(fileName)) {
|
|
1483
|
+
violations.push({
|
|
1484
|
+
file: filePath,
|
|
1485
|
+
rule: rule.rule_name,
|
|
1486
|
+
ruleType: rule.rule_type,
|
|
1487
|
+
severity: rule.severity,
|
|
1488
|
+
message: `File name does not match naming convention`,
|
|
1489
|
+
details: `Expected pattern: ${value.pattern}`
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
break;
|
|
1493
|
+
}
|
|
1494
|
+
default:
|
|
1495
|
+
break;
|
|
1496
|
+
}
|
|
1497
|
+
return violations;
|
|
1498
|
+
}
|
|
1499
|
+
function checkFunctionLines(content, lines, filePath, rule, limit) {
|
|
1500
|
+
const violations = [];
|
|
1501
|
+
const functionPatterns = [
|
|
1502
|
+
/(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)\s*\{/g,
|
|
1503
|
+
/(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>\s*\{/g,
|
|
1504
|
+
/(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?function\s*\([^)]*\)\s*\{/g,
|
|
1505
|
+
/(\w+)\s*\([^)]*\)\s*\{/g
|
|
1506
|
+
// Method in class/object
|
|
1507
|
+
];
|
|
1508
|
+
for (const pattern of functionPatterns) {
|
|
1509
|
+
let match;
|
|
1510
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
1511
|
+
const functionName = match[1];
|
|
1512
|
+
const startIndex = match.index + match[0].length - 1;
|
|
1513
|
+
let braceCount = 1;
|
|
1514
|
+
let endIndex = startIndex + 1;
|
|
1515
|
+
while (braceCount > 0 && endIndex < content.length) {
|
|
1516
|
+
if (content[endIndex] === "{") braceCount++;
|
|
1517
|
+
else if (content[endIndex] === "}") braceCount--;
|
|
1518
|
+
endIndex++;
|
|
1519
|
+
}
|
|
1520
|
+
const functionContent = content.substring(startIndex, endIndex);
|
|
1521
|
+
const functionLines = functionContent.split("\n").length;
|
|
1522
|
+
if (functionLines > limit) {
|
|
1523
|
+
const lineNumber = content.substring(0, match.index).split("\n").length;
|
|
1524
|
+
violations.push({
|
|
1525
|
+
file: filePath,
|
|
1526
|
+
rule: rule.rule_name,
|
|
1527
|
+
ruleType: rule.rule_type,
|
|
1528
|
+
severity: rule.severity,
|
|
1529
|
+
message: `Function "${functionName}" exceeds ${limit} lines`,
|
|
1530
|
+
line: lineNumber,
|
|
1531
|
+
details: `Current: ${functionLines} lines (limit: ${limit})`
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
return violations;
|
|
1537
|
+
}
|
|
1538
|
+
function formatViolations(violations) {
|
|
1539
|
+
for (const v of violations) {
|
|
1540
|
+
const severityColor = v.severity === "critical" ? chalk10.red : v.severity === "warning" ? chalk10.yellow : chalk10.blue;
|
|
1541
|
+
const lineInfo = v.line ? chalk10.dim(`:${v.line}`) : "";
|
|
1542
|
+
console.log(` ${severityColor(`[${v.severity.toUpperCase()}]`)} ${v.file}${lineInfo}`);
|
|
1543
|
+
console.log(` ${v.message}`);
|
|
1544
|
+
if (v.details) {
|
|
1545
|
+
console.log(` ${chalk10.dim(v.details)}`);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
function summarizeResults(results) {
|
|
1550
|
+
let criticalCount = 0;
|
|
1551
|
+
let warningCount = 0;
|
|
1552
|
+
let infoCount = 0;
|
|
1553
|
+
for (const result of results) {
|
|
1554
|
+
for (const v of result.violations) {
|
|
1555
|
+
if (v.severity === "critical") criticalCount++;
|
|
1556
|
+
else if (v.severity === "warning") warningCount++;
|
|
1557
|
+
else infoCount++;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
return {
|
|
1561
|
+
totalFiles: results.length,
|
|
1562
|
+
totalViolations: criticalCount + warningCount + infoCount,
|
|
1563
|
+
criticalCount,
|
|
1564
|
+
warningCount,
|
|
1565
|
+
infoCount
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
// src/commands/check.ts
|
|
1570
|
+
var CACHE_FILE2 = ".rigstate/rules-cache.json";
|
|
1571
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1572
|
+
var CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
1573
|
+
function createCheckCommand() {
|
|
1574
|
+
return new Command7("check").description("Validate code against Guardian architectural rules").argument("[path]", "Directory or file to check", ".").option("--project <id>", "Project ID (or use .rigstate manifest)").option("--strict [level]", 'Exit 1 on violations. Level: "all" (default) or "critical"').option("--staged", "Only check git staged files (for pre-commit hooks)").option("--json", "Output results as JSON").option("--no-cache", "Skip rule cache and fetch fresh from API").action(async (targetPath, options) => {
|
|
1575
|
+
const spinner = ora5();
|
|
1576
|
+
try {
|
|
1577
|
+
let projectId = options.project;
|
|
1578
|
+
let apiUrl = getApiUrl();
|
|
1579
|
+
if (!projectId) {
|
|
1580
|
+
const manifest = await loadManifest();
|
|
1581
|
+
if (manifest) {
|
|
1582
|
+
projectId = manifest.project_id;
|
|
1583
|
+
if (manifest.api_url) apiUrl = manifest.api_url;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
if (!projectId) {
|
|
1587
|
+
projectId = getProjectId();
|
|
1588
|
+
}
|
|
1589
|
+
if (!projectId) {
|
|
1590
|
+
console.log(chalk11.red("\u274C No project context found."));
|
|
1591
|
+
console.log(chalk11.dim(' Run "rigstate link" or pass --project <id>'));
|
|
1592
|
+
process.exit(2);
|
|
1593
|
+
}
|
|
1594
|
+
let apiKey;
|
|
1595
|
+
try {
|
|
1596
|
+
apiKey = getApiKey();
|
|
1597
|
+
} catch {
|
|
1598
|
+
console.log(chalk11.red('\u274C Not authenticated. Run "rigstate login" first.'));
|
|
1599
|
+
process.exit(2);
|
|
1600
|
+
}
|
|
1601
|
+
spinner.start("Fetching Guardian rules...");
|
|
1602
|
+
let rules;
|
|
1603
|
+
let settings;
|
|
1604
|
+
try {
|
|
1605
|
+
const cached = options.cache !== false ? await loadCachedRules(projectId) : null;
|
|
1606
|
+
if (cached && !isStale(cached.timestamp, CACHE_TTL_MS)) {
|
|
1607
|
+
rules = cached.rules;
|
|
1608
|
+
settings = cached.settings;
|
|
1609
|
+
spinner.text = "Using cached rules...";
|
|
1610
|
+
} else {
|
|
1611
|
+
const response = await axios7.get(`${apiUrl}/api/v1/guardian/rules`, {
|
|
1612
|
+
params: { project_id: projectId },
|
|
1613
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
1614
|
+
timeout: 1e4
|
|
1615
|
+
});
|
|
1616
|
+
if (!response.data.success) {
|
|
1617
|
+
throw new Error(response.data.error || "Unknown API error");
|
|
1618
|
+
}
|
|
1619
|
+
rules = response.data.data.rules;
|
|
1620
|
+
settings = response.data.data.settings;
|
|
1621
|
+
await saveCachedRules(projectId, rules, settings);
|
|
1622
|
+
}
|
|
1623
|
+
} catch (apiError) {
|
|
1624
|
+
const cached = await loadCachedRules(projectId);
|
|
1625
|
+
if (cached && !isStale(cached.timestamp, CACHE_MAX_AGE_MS)) {
|
|
1626
|
+
spinner.warn(chalk11.yellow("Using cached rules (API unavailable)"));
|
|
1627
|
+
rules = cached.rules;
|
|
1628
|
+
settings = cached.settings;
|
|
1629
|
+
} else {
|
|
1630
|
+
spinner.fail(chalk11.red("Failed to fetch rules and no valid cache"));
|
|
1631
|
+
console.log(chalk11.dim(` Error: ${apiError.message}`));
|
|
1632
|
+
process.exit(2);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
spinner.succeed(`Loaded ${rules.length} Guardian rules`);
|
|
1636
|
+
const scanPath = path13.resolve(process.cwd(), targetPath);
|
|
1637
|
+
let filesToCheck;
|
|
1638
|
+
if (options.staged) {
|
|
1639
|
+
spinner.start("Getting staged files...");
|
|
1640
|
+
try {
|
|
1641
|
+
const stagedOutput = execSync2("git diff --cached --name-only --diff-filter=ACMR", {
|
|
1642
|
+
encoding: "utf-8",
|
|
1643
|
+
cwd: process.cwd()
|
|
1644
|
+
});
|
|
1645
|
+
filesToCheck = stagedOutput.split("\n").filter((f) => f.trim()).filter((f) => isCodeFile2(f)).map((f) => path13.resolve(process.cwd(), f));
|
|
1646
|
+
} catch {
|
|
1647
|
+
spinner.fail("Not a git repository or no staged files");
|
|
1648
|
+
process.exit(2);
|
|
1649
|
+
}
|
|
1650
|
+
} else {
|
|
1651
|
+
spinner.start(`Scanning ${chalk11.cyan(targetPath)}...`);
|
|
1652
|
+
const pattern = path13.join(scanPath, "**/*");
|
|
1653
|
+
const allFiles = await glob3(pattern, {
|
|
1654
|
+
nodir: true,
|
|
1655
|
+
dot: false,
|
|
1656
|
+
ignore: [
|
|
1657
|
+
"**/node_modules/**",
|
|
1658
|
+
"**/.git/**",
|
|
1659
|
+
"**/dist/**",
|
|
1660
|
+
"**/build/**",
|
|
1661
|
+
"**/.next/**",
|
|
1662
|
+
"**/coverage/**"
|
|
1663
|
+
]
|
|
1664
|
+
});
|
|
1665
|
+
filesToCheck = allFiles.filter((f) => isCodeFile2(f));
|
|
1666
|
+
}
|
|
1667
|
+
if (filesToCheck.length === 0) {
|
|
1668
|
+
spinner.warn(chalk11.yellow("No code files found to check."));
|
|
1669
|
+
outputResults([], !!options.json);
|
|
1670
|
+
process.exit(0);
|
|
1671
|
+
}
|
|
1672
|
+
spinner.succeed(`Found ${filesToCheck.length} files to check`);
|
|
1673
|
+
spinner.start("Running Guardian checks...");
|
|
1674
|
+
const results = [];
|
|
1675
|
+
for (let i = 0; i < filesToCheck.length; i++) {
|
|
1676
|
+
const file = filesToCheck[i];
|
|
1677
|
+
spinner.text = `Checking ${i + 1}/${filesToCheck.length}: ${path13.basename(file)}`;
|
|
1678
|
+
const result = await checkFile(file, rules, process.cwd());
|
|
1679
|
+
results.push(result);
|
|
1680
|
+
}
|
|
1681
|
+
spinner.stop();
|
|
1682
|
+
const summary = summarizeResults(results);
|
|
1683
|
+
if (options.json) {
|
|
1684
|
+
outputResults(results, true);
|
|
1685
|
+
} else {
|
|
1686
|
+
outputResults(results, false);
|
|
1687
|
+
console.log("\n" + chalk11.bold("\u{1F4CA} Summary"));
|
|
1688
|
+
console.log(chalk11.dim("\u2500".repeat(50)));
|
|
1689
|
+
console.log(`Files checked: ${chalk11.cyan(summary.totalFiles)}`);
|
|
1690
|
+
console.log(`Total violations: ${summary.totalViolations > 0 ? chalk11.red(summary.totalViolations) : chalk11.green(0)}`);
|
|
1691
|
+
if (summary.totalViolations > 0) {
|
|
1692
|
+
console.log(` ${chalk11.red("Critical:")} ${summary.criticalCount}`);
|
|
1693
|
+
console.log(` ${chalk11.yellow("Warning:")} ${summary.warningCount}`);
|
|
1694
|
+
console.log(` ${chalk11.blue("Info:")} ${summary.infoCount}`);
|
|
1695
|
+
}
|
|
1696
|
+
console.log(chalk11.dim("\u2500".repeat(50)));
|
|
1697
|
+
}
|
|
1698
|
+
if (options.strict !== void 0) {
|
|
1699
|
+
const strictLevel = typeof options.strict === "string" ? options.strict : "all";
|
|
1700
|
+
if (strictLevel === "critical" && summary.criticalCount > 0) {
|
|
1701
|
+
console.log(chalk11.red("\n\u274C Check failed: Critical violations found"));
|
|
1702
|
+
process.exit(1);
|
|
1703
|
+
} else if (strictLevel === "all" && summary.totalViolations > 0) {
|
|
1704
|
+
console.log(chalk11.red("\n\u274C Check failed: Violations found"));
|
|
1705
|
+
process.exit(1);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
if (summary.totalViolations === 0) {
|
|
1709
|
+
console.log(chalk11.green("\n\u2705 All checks passed!"));
|
|
1710
|
+
}
|
|
1711
|
+
process.exit(0);
|
|
1712
|
+
} catch (error) {
|
|
1713
|
+
spinner.fail(chalk11.red("Check failed"));
|
|
1714
|
+
console.error(chalk11.red("Error:"), error.message);
|
|
1715
|
+
process.exit(2);
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
function isCodeFile2(filePath) {
|
|
1720
|
+
const codeExtensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1721
|
+
const ext = path13.extname(filePath).toLowerCase();
|
|
1722
|
+
return codeExtensions.includes(ext);
|
|
1723
|
+
}
|
|
1724
|
+
async function loadCachedRules(projectId) {
|
|
1725
|
+
try {
|
|
1726
|
+
const cachePath = path13.join(process.cwd(), CACHE_FILE2);
|
|
1727
|
+
const content = await fs12.readFile(cachePath, "utf-8");
|
|
1728
|
+
const cached = JSON.parse(content);
|
|
1729
|
+
if (cached.projectId !== projectId) {
|
|
1730
|
+
return null;
|
|
1731
|
+
}
|
|
1732
|
+
return cached;
|
|
1733
|
+
} catch {
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
async function saveCachedRules(projectId, rules, settings) {
|
|
1738
|
+
try {
|
|
1739
|
+
const cacheDir = path13.join(process.cwd(), ".rigstate");
|
|
1740
|
+
await fs12.mkdir(cacheDir, { recursive: true });
|
|
1741
|
+
const cached = {
|
|
1742
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1743
|
+
projectId,
|
|
1744
|
+
rules,
|
|
1745
|
+
settings
|
|
1746
|
+
};
|
|
1747
|
+
await fs12.writeFile(
|
|
1748
|
+
path13.join(cacheDir, "rules-cache.json"),
|
|
1749
|
+
JSON.stringify(cached, null, 2)
|
|
1750
|
+
);
|
|
1751
|
+
} catch {
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
function isStale(timestamp, maxAge) {
|
|
1755
|
+
const age = Date.now() - new Date(timestamp).getTime();
|
|
1756
|
+
return age > maxAge;
|
|
1757
|
+
}
|
|
1758
|
+
function outputResults(results, json) {
|
|
1759
|
+
if (json) {
|
|
1760
|
+
console.log(JSON.stringify({
|
|
1761
|
+
results,
|
|
1762
|
+
summary: summarizeResults(results)
|
|
1763
|
+
}, null, 2));
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
const hasViolations = results.some((r) => r.violations.length > 0);
|
|
1767
|
+
if (!hasViolations) {
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
console.log("\n" + chalk11.bold("\u{1F50D} Violations Found"));
|
|
1771
|
+
console.log(chalk11.dim("\u2500".repeat(50)));
|
|
1772
|
+
for (const result of results) {
|
|
1773
|
+
if (result.violations.length > 0) {
|
|
1774
|
+
formatViolations(result.violations);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
// src/commands/hooks.ts
|
|
1780
|
+
init_esm_shims();
|
|
1781
|
+
import { Command as Command8 } from "commander";
|
|
1782
|
+
import chalk12 from "chalk";
|
|
1783
|
+
import fs13 from "fs/promises";
|
|
1784
|
+
import path14 from "path";
|
|
1785
|
+
var PRE_COMMIT_SCRIPT = `#!/bin/sh
|
|
1786
|
+
# Rigstate Guardian Pre-commit Hook
|
|
1787
|
+
# Installed by: rigstate hooks install
|
|
1788
|
+
|
|
1789
|
+
# 1. Silent Sentinel Check (Phase 5)
|
|
1790
|
+
if [ -f .rigstate/guardian.lock ]; then
|
|
1791
|
+
echo "\u{1F6D1} INTERVENTION ACTIVE: Commit blocked by Silent Sentinel."
|
|
1792
|
+
echo " A critical violation ('HARD_LOCK') was detected by the Guardian Daemon."
|
|
1793
|
+
echo " Please fix the violation to unlock the repo."
|
|
1794
|
+
echo ""
|
|
1795
|
+
if grep -q "HARD_LOCK_ACTIVE" .rigstate/guardian.lock; then
|
|
1796
|
+
cat .rigstate/guardian.lock
|
|
1797
|
+
fi
|
|
1798
|
+
exit 1
|
|
1799
|
+
fi
|
|
1800
|
+
|
|
1801
|
+
echo "\u{1F6E1}\uFE0F Running Guardian checks..."
|
|
1802
|
+
|
|
1803
|
+
# Run check with strict mode for critical violations
|
|
1804
|
+
rigstate check --staged --strict=critical
|
|
1805
|
+
|
|
1806
|
+
# Exit with the same code as rigstate check
|
|
1807
|
+
exit $?
|
|
1808
|
+
`;
|
|
1809
|
+
function createHooksCommand() {
|
|
1810
|
+
const hooks = new Command8("hooks").description("Manage git hooks for Guardian integration");
|
|
1811
|
+
hooks.command("install").description("Install pre-commit hook to run Guardian checks").option("--strict [level]", 'Strict level: "all" or "critical" (default)', "critical").action(async (options) => {
|
|
1812
|
+
try {
|
|
1813
|
+
const gitDir = path14.join(process.cwd(), ".git");
|
|
1814
|
+
try {
|
|
1815
|
+
await fs13.access(gitDir);
|
|
1816
|
+
} catch {
|
|
1817
|
+
console.log(chalk12.red("\u274C Not a git repository."));
|
|
1818
|
+
console.log(chalk12.dim(' Initialize with "git init" first.'));
|
|
1819
|
+
process.exit(1);
|
|
1820
|
+
}
|
|
1821
|
+
const hooksDir = path14.join(gitDir, "hooks");
|
|
1822
|
+
await fs13.mkdir(hooksDir, { recursive: true });
|
|
1823
|
+
const preCommitPath = path14.join(hooksDir, "pre-commit");
|
|
1824
|
+
let existingContent = "";
|
|
1825
|
+
try {
|
|
1826
|
+
existingContent = await fs13.readFile(preCommitPath, "utf-8");
|
|
1827
|
+
if (existingContent.includes("rigstate")) {
|
|
1828
|
+
console.log(chalk12.yellow("\u26A0 Rigstate pre-commit hook already installed."));
|
|
1829
|
+
console.log(chalk12.dim(' Use "rigstate hooks uninstall" to remove first.'));
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
} catch {
|
|
1833
|
+
}
|
|
1834
|
+
let script = PRE_COMMIT_SCRIPT;
|
|
1835
|
+
if (options.strict === "all") {
|
|
1836
|
+
script = script.replace("--strict=critical", "--strict");
|
|
1837
|
+
}
|
|
1838
|
+
if (existingContent && !existingContent.includes("rigstate")) {
|
|
1839
|
+
const combinedScript = existingContent + "\n\n" + script.replace("#!/bin/sh\n", "");
|
|
1840
|
+
await fs13.writeFile(preCommitPath, combinedScript, { mode: 493 });
|
|
1841
|
+
console.log(chalk12.green("\u2705 Rigstate hook appended to existing pre-commit."));
|
|
1842
|
+
} else {
|
|
1843
|
+
await fs13.writeFile(preCommitPath, script, { mode: 493 });
|
|
1844
|
+
console.log(chalk12.green("\u2705 Pre-commit hook installed!"));
|
|
1845
|
+
}
|
|
1846
|
+
console.log(chalk12.dim(` Path: ${preCommitPath}`));
|
|
1847
|
+
console.log(chalk12.dim(` Strict level: ${options.strict}`));
|
|
1848
|
+
console.log("");
|
|
1849
|
+
console.log(chalk12.cyan("Guardian will now check your code before each commit."));
|
|
1850
|
+
console.log(chalk12.dim('Use "rigstate hooks uninstall" to remove the hook.'));
|
|
1851
|
+
} catch (error) {
|
|
1852
|
+
console.error(chalk12.red("Failed to install hook:"), error.message);
|
|
1853
|
+
process.exit(1);
|
|
1854
|
+
}
|
|
1855
|
+
});
|
|
1856
|
+
hooks.command("uninstall").description("Remove Rigstate pre-commit hook").action(async () => {
|
|
1857
|
+
try {
|
|
1858
|
+
const preCommitPath = path14.join(process.cwd(), ".git", "hooks", "pre-commit");
|
|
1859
|
+
try {
|
|
1860
|
+
const content = await fs13.readFile(preCommitPath, "utf-8");
|
|
1861
|
+
if (!content.includes("rigstate")) {
|
|
1862
|
+
console.log(chalk12.yellow("\u26A0 No Rigstate hook found in pre-commit."));
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
if (content.includes("# Rigstate Guardian Pre-commit Hook") && content.trim().split("\n").filter((l) => l && !l.startsWith("#")).length <= 4) {
|
|
1866
|
+
await fs13.unlink(preCommitPath);
|
|
1867
|
+
console.log(chalk12.green("\u2705 Pre-commit hook removed."));
|
|
1868
|
+
} else {
|
|
1869
|
+
const lines = content.split("\n");
|
|
1870
|
+
const filteredLines = [];
|
|
1871
|
+
let inRigstateSection = false;
|
|
1872
|
+
for (const line of lines) {
|
|
1873
|
+
if (line.includes("Rigstate Guardian Pre-commit Hook")) {
|
|
1874
|
+
inRigstateSection = true;
|
|
1875
|
+
continue;
|
|
1876
|
+
}
|
|
1877
|
+
if (inRigstateSection && line.includes("exit $?")) {
|
|
1878
|
+
inRigstateSection = false;
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
if (!inRigstateSection && !line.includes("rigstate check")) {
|
|
1882
|
+
filteredLines.push(line);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
await fs13.writeFile(preCommitPath, filteredLines.join("\n"), { mode: 493 });
|
|
1886
|
+
console.log(chalk12.green("\u2705 Rigstate section removed from pre-commit hook."));
|
|
1887
|
+
}
|
|
1888
|
+
} catch {
|
|
1889
|
+
console.log(chalk12.yellow("\u26A0 No pre-commit hook found."));
|
|
1890
|
+
}
|
|
1891
|
+
} catch (error) {
|
|
1892
|
+
console.error(chalk12.red("Failed to uninstall hook:"), error.message);
|
|
1893
|
+
process.exit(1);
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
return hooks;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
// src/commands/daemon.ts
|
|
1900
|
+
init_esm_shims();
|
|
1901
|
+
import { Command as Command9 } from "commander";
|
|
1902
|
+
import chalk15 from "chalk";
|
|
1903
|
+
import ora6 from "ora";
|
|
1904
|
+
import fs17 from "fs/promises";
|
|
1905
|
+
import path19 from "path";
|
|
1906
|
+
|
|
1907
|
+
// src/daemon/factory.ts
|
|
1908
|
+
init_esm_shims();
|
|
1909
|
+
|
|
1910
|
+
// src/daemon/core.ts
|
|
1911
|
+
init_esm_shims();
|
|
1912
|
+
import chalk14 from "chalk";
|
|
1913
|
+
import * as fs16 from "fs/promises";
|
|
1914
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
1915
|
+
|
|
1916
|
+
// src/daemon/file-watcher.ts
|
|
1917
|
+
init_esm_shims();
|
|
1918
|
+
import * as chokidar from "chokidar";
|
|
1919
|
+
import path15 from "path";
|
|
1920
|
+
import { EventEmitter } from "events";
|
|
1921
|
+
var CODE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1922
|
+
function isCodeFile3(filePath) {
|
|
1923
|
+
const ext = path15.extname(filePath).toLowerCase();
|
|
1924
|
+
return CODE_EXTENSIONS.includes(ext);
|
|
1925
|
+
}
|
|
1926
|
+
function createFileWatcher(watchPath) {
|
|
1927
|
+
const emitter = new EventEmitter();
|
|
1928
|
+
let watcher = null;
|
|
1929
|
+
emitter.start = () => {
|
|
1930
|
+
const absolutePath = path15.resolve(process.cwd(), watchPath);
|
|
1931
|
+
watcher = chokidar.watch(absolutePath, {
|
|
1932
|
+
ignored: (path24) => {
|
|
1933
|
+
if (path24.includes("node_modules")) return true;
|
|
1934
|
+
if (path24.includes(".git")) return true;
|
|
1935
|
+
if (path24.includes(".next")) return true;
|
|
1936
|
+
if (path24.includes("dist")) return true;
|
|
1937
|
+
if (path24.includes("build")) return true;
|
|
1938
|
+
if (path24.includes(".rigstate")) return true;
|
|
1939
|
+
if (path24.includes("coverage")) return true;
|
|
1940
|
+
return false;
|
|
1941
|
+
},
|
|
1942
|
+
persistent: true,
|
|
1943
|
+
ignoreInitial: true,
|
|
1944
|
+
depth: 15,
|
|
1945
|
+
awaitWriteFinish: {
|
|
1946
|
+
stabilityThreshold: 200,
|
|
1947
|
+
pollInterval: 100
|
|
1948
|
+
},
|
|
1949
|
+
usePolling: false,
|
|
1950
|
+
// Use native events when possible
|
|
1951
|
+
interval: 300,
|
|
1952
|
+
binaryInterval: 1e3
|
|
1953
|
+
});
|
|
1954
|
+
watcher.on("change", (filePath) => {
|
|
1955
|
+
if (isCodeFile3(filePath)) {
|
|
1956
|
+
emitter.emit("change", path15.relative(process.cwd(), filePath));
|
|
1957
|
+
}
|
|
1958
|
+
});
|
|
1959
|
+
watcher.on("add", (filePath) => {
|
|
1960
|
+
if (isCodeFile3(filePath)) {
|
|
1961
|
+
emitter.emit("add", path15.relative(process.cwd(), filePath));
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
watcher.on("unlink", (filePath) => {
|
|
1965
|
+
if (isCodeFile3(filePath)) {
|
|
1966
|
+
emitter.emit("unlink", path15.relative(process.cwd(), filePath));
|
|
1967
|
+
}
|
|
1968
|
+
});
|
|
1969
|
+
watcher.on("error", (error) => {
|
|
1970
|
+
emitter.emit("error", error);
|
|
1971
|
+
});
|
|
1972
|
+
watcher.on("ready", () => {
|
|
1973
|
+
emitter.emit("ready");
|
|
1974
|
+
});
|
|
1975
|
+
};
|
|
1976
|
+
emitter.stop = async () => {
|
|
1977
|
+
if (watcher) {
|
|
1978
|
+
await watcher.close();
|
|
1979
|
+
watcher = null;
|
|
1980
|
+
}
|
|
1981
|
+
};
|
|
1982
|
+
return emitter;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// src/daemon/heuristic-engine.ts
|
|
1986
|
+
init_esm_shims();
|
|
1987
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
1988
|
+
import { dirname } from "path";
|
|
1989
|
+
import path16 from "path";
|
|
1990
|
+
import axios8 from "axios";
|
|
1991
|
+
var GLOBAL_HEURISTICS = [
|
|
1992
|
+
{
|
|
1993
|
+
skillId: "payment-expert",
|
|
1994
|
+
patterns: {
|
|
1995
|
+
imports: ["@stripe/", "stripe"],
|
|
1996
|
+
content: ["PaymentIntent", "CheckoutSession"]
|
|
1997
|
+
},
|
|
1998
|
+
confidence: "high"
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
skillId: "rigstate-integrity-gate",
|
|
2002
|
+
patterns: {
|
|
2003
|
+
files: ["**/release.config.js", "**/manifest.json", "**/.rigstate/release/*"],
|
|
2004
|
+
content: ["[CORE INTEGRITY]", "prepare_release"]
|
|
2005
|
+
},
|
|
2006
|
+
confidence: "high"
|
|
2007
|
+
},
|
|
2008
|
+
{
|
|
2009
|
+
skillId: "database-architect",
|
|
2010
|
+
patterns: {
|
|
2011
|
+
files: ["**/*.sql", "**/schema.prisma", "**/migrations/*"],
|
|
2012
|
+
imports: ["@supabase/supabase-js", "drizzle-orm", "prisma"]
|
|
2013
|
+
},
|
|
2014
|
+
confidence: "medium"
|
|
2015
|
+
}
|
|
2016
|
+
];
|
|
2017
|
+
var HeuristicEngine = class {
|
|
2018
|
+
rules = [];
|
|
2019
|
+
cachePath;
|
|
2020
|
+
constructor() {
|
|
2021
|
+
this.cachePath = path16.join(process.cwd(), ".rigstate", "cache", "heuristics.json");
|
|
2022
|
+
this.loadRules();
|
|
2023
|
+
}
|
|
2024
|
+
async loadRules() {
|
|
2025
|
+
try {
|
|
2026
|
+
const cached = await readFile(this.cachePath, "utf-8");
|
|
2027
|
+
const data = JSON.parse(cached);
|
|
2028
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
2029
|
+
this.rules = data;
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
} catch (e) {
|
|
2033
|
+
}
|
|
2034
|
+
this.rules = GLOBAL_HEURISTICS;
|
|
2035
|
+
}
|
|
2036
|
+
async refreshRules(projectId, apiUrl, apiKey) {
|
|
2037
|
+
try {
|
|
2038
|
+
await mkdir(dirname(this.cachePath), { recursive: true });
|
|
2039
|
+
const endpoint = `${apiUrl}/api/v1/skills/triggers`;
|
|
2040
|
+
const response = await axios8.get(endpoint, {
|
|
2041
|
+
headers: {
|
|
2042
|
+
"x-api-key": apiKey,
|
|
2043
|
+
"Content-Type": "application/json"
|
|
2044
|
+
}
|
|
2045
|
+
});
|
|
2046
|
+
if (response.data && Array.isArray(response.data.triggers)) {
|
|
2047
|
+
const cloudRules = response.data.triggers;
|
|
2048
|
+
await writeFile(this.cachePath, JSON.stringify(cloudRules, null, 2));
|
|
2049
|
+
this.rules = cloudRules;
|
|
2050
|
+
return true;
|
|
2051
|
+
}
|
|
2052
|
+
} catch (error) {
|
|
2053
|
+
return false;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
async analyzeFile(filePath, metrics) {
|
|
2057
|
+
try {
|
|
2058
|
+
const content = await readFile(filePath, "utf-8");
|
|
2059
|
+
const matches = [];
|
|
2060
|
+
const activeRules = this.rules.length > 0 ? this.rules : GLOBAL_HEURISTICS;
|
|
2061
|
+
for (const heuristic of activeRules) {
|
|
2062
|
+
const match = this.checkHeuristic(filePath, content, heuristic, metrics);
|
|
2063
|
+
if (match) {
|
|
2064
|
+
matches.push(match);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
return matches;
|
|
2068
|
+
} catch (error) {
|
|
2069
|
+
return [];
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
checkHeuristic(filePath, content, heuristic, metrics) {
|
|
2073
|
+
if (heuristic.patterns.metric_threshold && metrics) {
|
|
2074
|
+
const lineLimitRule = metrics.rules.find((r) => r.rule_type === "MAX_FILE_LINES");
|
|
2075
|
+
if (lineLimitRule) {
|
|
2076
|
+
const limit = lineLimitRule.value.limit;
|
|
2077
|
+
const threshold = limit * heuristic.patterns.metric_threshold;
|
|
2078
|
+
if (metrics.lineCount >= threshold && metrics.lineCount < limit) {
|
|
2079
|
+
return {
|
|
2080
|
+
skillId: heuristic.skillId,
|
|
2081
|
+
file: filePath,
|
|
2082
|
+
reason: `File reached ${Math.round(metrics.lineCount / limit * 100)}% of its line limit (${metrics.lineCount}/${limit})`,
|
|
2083
|
+
confidence: "high"
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
if (heuristic.patterns.files) {
|
|
2089
|
+
const matchesFile = heuristic.patterns.files.some((pattern) => {
|
|
2090
|
+
if (pattern.startsWith("**/*")) return filePath.endsWith(pattern.replace("**/*", ""));
|
|
2091
|
+
return filePath.includes(pattern);
|
|
2092
|
+
});
|
|
2093
|
+
if (matchesFile) {
|
|
2094
|
+
return {
|
|
2095
|
+
skillId: heuristic.skillId,
|
|
2096
|
+
file: filePath,
|
|
2097
|
+
reason: `Matches file pattern: ${heuristic.patterns.files.join(", ")}`,
|
|
2098
|
+
confidence: heuristic.confidence
|
|
2099
|
+
};
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
if (heuristic.patterns.imports) {
|
|
2103
|
+
for (const imp of heuristic.patterns.imports) {
|
|
2104
|
+
const importRegex = new RegExp(`(import .* from ['"]${imp}|require\\(['"]${imp})`, "i");
|
|
2105
|
+
if (importRegex.test(content)) {
|
|
2106
|
+
return {
|
|
2107
|
+
skillId: heuristic.skillId,
|
|
2108
|
+
file: filePath,
|
|
2109
|
+
reason: `Detected import: ${imp}`,
|
|
2110
|
+
confidence: heuristic.confidence
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
if (heuristic.patterns.content) {
|
|
2116
|
+
for (const pattern of heuristic.patterns.content) {
|
|
2117
|
+
if (content.includes(pattern)) {
|
|
2118
|
+
return {
|
|
2119
|
+
skillId: heuristic.skillId,
|
|
2120
|
+
file: filePath,
|
|
2121
|
+
reason: `Detected content pattern: ${pattern}`,
|
|
2122
|
+
confidence: heuristic.confidence
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
return null;
|
|
2128
|
+
}
|
|
2129
|
+
};
|
|
2130
|
+
function createHeuristicEngine() {
|
|
2131
|
+
return new HeuristicEngine();
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
// src/daemon/intervention-protocol.ts
|
|
2135
|
+
init_esm_shims();
|
|
2136
|
+
import chalk13 from "chalk";
|
|
2137
|
+
import * as fs14 from "fs";
|
|
2138
|
+
import * as path17 from "path";
|
|
2139
|
+
var InterventionProtocol = class {
|
|
2140
|
+
activeViolators = /* @__PURE__ */ new Set();
|
|
2141
|
+
/**
|
|
2142
|
+
* Registers a violation outcome to update the global lock state.
|
|
2143
|
+
*/
|
|
2144
|
+
registerViolation(filePath, decision) {
|
|
2145
|
+
if (decision.mode === "HARD_LOCK") {
|
|
2146
|
+
this.activeViolators.add(filePath);
|
|
2147
|
+
this.syncLockFile();
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Clears any active locks for a specific file (e.g. after a fix).
|
|
2152
|
+
*/
|
|
2153
|
+
clear(filePath) {
|
|
2154
|
+
if (this.activeViolators.has(filePath)) {
|
|
2155
|
+
this.activeViolators.delete(filePath);
|
|
2156
|
+
this.syncLockFile();
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
syncLockFile() {
|
|
2160
|
+
try {
|
|
2161
|
+
const lockDir = path17.join(process.cwd(), ".rigstate");
|
|
2162
|
+
if (!fs14.existsSync(lockDir)) fs14.mkdirSync(lockDir, { recursive: true });
|
|
2163
|
+
const lockPath = path17.join(lockDir, "guardian.lock");
|
|
2164
|
+
if (this.activeViolators.size > 0) {
|
|
2165
|
+
const content = `HARD_LOCK_ACTIVE
|
|
2166
|
+
Timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
2167
|
+
|
|
2168
|
+
Blocking Files:
|
|
2169
|
+
${Array.from(this.activeViolators).join("\n")}`;
|
|
2170
|
+
fs14.writeFileSync(lockPath, content, "utf-8");
|
|
2171
|
+
} else {
|
|
2172
|
+
if (fs14.existsSync(lockPath)) fs14.unlinkSync(lockPath);
|
|
2173
|
+
}
|
|
2174
|
+
} catch (e) {
|
|
2175
|
+
console.error("Failed to sync guardian lock file:", e);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
/**
|
|
2179
|
+
* Evaluate a Heuristic Trigger (Preventative)
|
|
2180
|
+
*/
|
|
2181
|
+
evaluateTrigger(skillId, confidence) {
|
|
2182
|
+
if (skillId === "rigstate-integrity-gate") {
|
|
2183
|
+
return {
|
|
2184
|
+
mode: "SOFT_LOCK",
|
|
2185
|
+
message: "Integrity Gate detected. Release Manifest required before final push.",
|
|
2186
|
+
blockCommit: false
|
|
2187
|
+
// Soft lock just warns
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2190
|
+
return {
|
|
2191
|
+
mode: "OPEN",
|
|
2192
|
+
message: `Predictive activation: ${skillId}`,
|
|
2193
|
+
blockCommit: false
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Evaluate a Guardian Violation (Corrective)
|
|
2198
|
+
*/
|
|
2199
|
+
evaluateViolation(ruleId, severity) {
|
|
2200
|
+
if (severity === "critical" || severity === "error") {
|
|
2201
|
+
return {
|
|
2202
|
+
mode: "HARD_LOCK",
|
|
2203
|
+
message: `CRITICAL VIOLATION: ${ruleId}. System Integrity Compromised.`,
|
|
2204
|
+
blockCommit: true
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
if (severity === "warning") {
|
|
2208
|
+
return {
|
|
2209
|
+
mode: "SOFT_LOCK",
|
|
2210
|
+
message: `Warning: ${ruleId}. Review recommended.`,
|
|
2211
|
+
blockCommit: false
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
return {
|
|
2215
|
+
mode: "OPEN",
|
|
2216
|
+
message: "Info notice.",
|
|
2217
|
+
blockCommit: false
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
/**
|
|
2221
|
+
* Logs the intervention to the console with appropriate visual weight
|
|
2222
|
+
*/
|
|
2223
|
+
enforce(decision) {
|
|
2224
|
+
if (decision.mode === "OPEN") return;
|
|
2225
|
+
const icon = decision.mode === "HARD_LOCK" ? "\u{1F6AB}" : "\u26A0\uFE0F";
|
|
2226
|
+
const color = decision.mode === "HARD_LOCK" ? chalk13.bgRed.white.bold : chalk13.yellow.bold;
|
|
2227
|
+
console.log("\n" + color(` ${icon} [${decision.mode}] INTERVENTION `));
|
|
2228
|
+
console.log(chalk13.redBright(` ${decision.message}`));
|
|
2229
|
+
if (decision.blockCommit) {
|
|
2230
|
+
console.log(chalk13.dim(" \u{1F512} Commit functionality is logically suspended until fixed."));
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
function createInterventionProtocol() {
|
|
2235
|
+
return new InterventionProtocol();
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
// src/daemon/guardian-monitor.ts
|
|
2239
|
+
init_esm_shims();
|
|
2240
|
+
import axios9 from "axios";
|
|
2241
|
+
import fs15 from "fs/promises";
|
|
2242
|
+
import path18 from "path";
|
|
2243
|
+
var CACHE_FILE3 = ".rigstate/rules-cache.json";
|
|
2244
|
+
var CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
2245
|
+
function createGuardianMonitor(projectId, apiUrl, apiKey) {
|
|
2246
|
+
let rules = [];
|
|
2247
|
+
let lastFetch = 0;
|
|
2248
|
+
const loadRules = async () => {
|
|
2249
|
+
if (rules.length > 0 && Date.now() - lastFetch < CACHE_TTL_MS2) {
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
try {
|
|
2253
|
+
const response = await axios9.get(`${apiUrl}/api/v1/guardian/rules`, {
|
|
2254
|
+
params: { project_id: projectId },
|
|
2255
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
2256
|
+
timeout: 1e4
|
|
2257
|
+
});
|
|
2258
|
+
if (response.data.success && response.data.data.rules) {
|
|
2259
|
+
rules = response.data.data.rules;
|
|
2260
|
+
lastFetch = Date.now();
|
|
2261
|
+
await saveCachedRules2(projectId, rules);
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
} catch (error) {
|
|
2265
|
+
const cached = await loadCachedRules2(projectId);
|
|
2266
|
+
if (cached) {
|
|
2267
|
+
rules = cached.rules;
|
|
2268
|
+
lastFetch = Date.now();
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
rules = [];
|
|
2273
|
+
};
|
|
2274
|
+
const checkFileImpl = async (filePath) => {
|
|
2275
|
+
await loadRules();
|
|
2276
|
+
if (rules.length === 0) {
|
|
2277
|
+
return {
|
|
2278
|
+
file: filePath,
|
|
2279
|
+
violations: [],
|
|
2280
|
+
passed: true
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
const absolutePath = path18.resolve(process.cwd(), filePath);
|
|
2284
|
+
return checkFile(absolutePath, rules, process.cwd());
|
|
2285
|
+
};
|
|
2286
|
+
const getRuleCount = () => rules.length;
|
|
2287
|
+
const getRules = () => rules;
|
|
2288
|
+
return {
|
|
2289
|
+
loadRules,
|
|
2290
|
+
checkFile: checkFileImpl,
|
|
2291
|
+
getRuleCount,
|
|
2292
|
+
getRules
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
async function loadCachedRules2(projectId) {
|
|
2296
|
+
try {
|
|
2297
|
+
const cachePath = path18.join(process.cwd(), CACHE_FILE3);
|
|
2298
|
+
const content = await fs15.readFile(cachePath, "utf-8");
|
|
2299
|
+
const cached = JSON.parse(content);
|
|
2300
|
+
if (cached.projectId !== projectId) {
|
|
2301
|
+
return null;
|
|
2302
|
+
}
|
|
2303
|
+
return cached;
|
|
2304
|
+
} catch {
|
|
2305
|
+
return null;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
async function saveCachedRules2(projectId, rules) {
|
|
2309
|
+
try {
|
|
2310
|
+
const cacheDir = path18.join(process.cwd(), ".rigstate");
|
|
2311
|
+
await fs15.mkdir(cacheDir, { recursive: true });
|
|
2312
|
+
const cached = {
|
|
2313
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2314
|
+
projectId,
|
|
2315
|
+
rules,
|
|
2316
|
+
settings: { lmax: 400, lmax_warning: 350 }
|
|
2317
|
+
};
|
|
2318
|
+
await fs15.writeFile(
|
|
2319
|
+
path18.join(cacheDir, "rules-cache.json"),
|
|
2320
|
+
JSON.stringify(cached, null, 2)
|
|
2321
|
+
);
|
|
2322
|
+
} catch {
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
// src/daemon/bridge-listener.ts
|
|
2327
|
+
init_esm_shims();
|
|
2328
|
+
import axios10 from "axios";
|
|
2329
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
2330
|
+
var POLL_INTERVAL_MS = 5e3;
|
|
2331
|
+
function createBridgeListener(projectId, apiUrl, apiKey) {
|
|
2332
|
+
const emitter = new EventEmitter2();
|
|
2333
|
+
let pollInterval = null;
|
|
2334
|
+
let isConnected = false;
|
|
2335
|
+
let lastCheckedId = null;
|
|
2336
|
+
const checkBridge = async () => {
|
|
2337
|
+
try {
|
|
2338
|
+
const response = await axios10.get(`${apiUrl}/api/v1/agent/bridge`, {
|
|
2339
|
+
params: {
|
|
2340
|
+
project_id: projectId,
|
|
2341
|
+
action: "check"
|
|
2342
|
+
},
|
|
2343
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
2344
|
+
timeout: 1e4
|
|
2345
|
+
});
|
|
2346
|
+
if (response.data.success && response.data.data?.task) {
|
|
2347
|
+
const task = response.data.data.task;
|
|
2348
|
+
if (task.id !== lastCheckedId) {
|
|
2349
|
+
lastCheckedId = task.id;
|
|
2350
|
+
if (task.proposal?.startsWith("ping")) {
|
|
2351
|
+
emitter.emit("ping");
|
|
2352
|
+
await acknowledgePing(task.id);
|
|
2353
|
+
} else {
|
|
2354
|
+
emitter.emit("task", task);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
} catch (error) {
|
|
2359
|
+
if (error.code !== "ECONNREFUSED" && error.code !== "ETIMEDOUT") {
|
|
2360
|
+
emitter.emit("error", error);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
};
|
|
2364
|
+
const acknowledgePing = async (taskId) => {
|
|
2365
|
+
try {
|
|
2366
|
+
await axios10.post(`${apiUrl}/api/v1/agent/bridge`, {
|
|
2367
|
+
project_id: projectId,
|
|
2368
|
+
action: "update",
|
|
2369
|
+
bridge_id: taskId,
|
|
2370
|
+
status: "COMPLETED",
|
|
2371
|
+
summary: "Pong! Guardian Daemon is active."
|
|
2372
|
+
}, {
|
|
2373
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
2374
|
+
timeout: 5e3
|
|
2375
|
+
});
|
|
2376
|
+
} catch {
|
|
2377
|
+
}
|
|
2378
|
+
};
|
|
2379
|
+
emitter.connect = async () => {
|
|
2380
|
+
if (isConnected) return;
|
|
2381
|
+
await checkBridge();
|
|
2382
|
+
pollInterval = setInterval(checkBridge, POLL_INTERVAL_MS);
|
|
2383
|
+
isConnected = true;
|
|
2384
|
+
emitter.emit("connected");
|
|
2385
|
+
};
|
|
2386
|
+
emitter.disconnect = async () => {
|
|
2387
|
+
if (pollInterval) {
|
|
2388
|
+
clearInterval(pollInterval);
|
|
2389
|
+
pollInterval = null;
|
|
2390
|
+
}
|
|
2391
|
+
isConnected = false;
|
|
2392
|
+
emitter.emit("disconnected");
|
|
2393
|
+
};
|
|
2394
|
+
return emitter;
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
// src/daemon/telemetry.ts
|
|
2398
|
+
init_esm_shims();
|
|
2399
|
+
import axios11 from "axios";
|
|
2400
|
+
async function trackSkillUsage(apiUrl, apiKey, projectId, skillId) {
|
|
2401
|
+
try {
|
|
2402
|
+
await axios11.post(`${apiUrl}/api/v1/skills/usage`, {
|
|
2403
|
+
projectId,
|
|
2404
|
+
skillName: skillId,
|
|
2405
|
+
status: "ACTIVATED"
|
|
2406
|
+
}, {
|
|
2407
|
+
headers: { "x-api-key": apiKey }
|
|
2408
|
+
});
|
|
2409
|
+
} catch (e) {
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// src/daemon/core.ts
|
|
2414
|
+
init_skills_provisioner();
|
|
2415
|
+
var GuardianDaemon = class extends EventEmitter3 {
|
|
2416
|
+
config;
|
|
2417
|
+
state;
|
|
2418
|
+
fileWatcher = null;
|
|
2419
|
+
guardianMonitor = null;
|
|
2420
|
+
heuristicEngine = null;
|
|
2421
|
+
interventionProtocol = null;
|
|
2422
|
+
bridgeListener = null;
|
|
2423
|
+
constructor(config2) {
|
|
2424
|
+
super();
|
|
2425
|
+
this.config = config2;
|
|
2426
|
+
this.state = {
|
|
2427
|
+
isRunning: false,
|
|
2428
|
+
startedAt: null,
|
|
2429
|
+
filesChecked: 0,
|
|
2430
|
+
violationsFound: 0,
|
|
2431
|
+
tasksProcessed: 0,
|
|
2432
|
+
lastActivity: null
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
async start() {
|
|
2436
|
+
if (this.state.isRunning) {
|
|
2437
|
+
console.log(chalk14.yellow("Daemon is already running."));
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
this.printWelcome();
|
|
2441
|
+
this.state.isRunning = true;
|
|
2442
|
+
this.state.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2443
|
+
this.heuristicEngine = createHeuristicEngine();
|
|
2444
|
+
this.interventionProtocol = createInterventionProtocol();
|
|
2445
|
+
this.guardianMonitor = createGuardianMonitor(this.config.projectId, this.config.apiUrl, this.config.apiKey);
|
|
2446
|
+
await this.guardianMonitor.loadRules();
|
|
2447
|
+
console.log(chalk14.green(` \u2713 Loaded ${this.guardianMonitor.getRuleCount()} rules`));
|
|
2448
|
+
await this.syncHeuristics();
|
|
2449
|
+
if (this.config.checkOnChange) {
|
|
2450
|
+
this.setupFileWatcher();
|
|
2451
|
+
}
|
|
2452
|
+
if (this.config.bridgeEnabled) {
|
|
2453
|
+
await this.setupBridge();
|
|
2454
|
+
}
|
|
2455
|
+
this.printActive();
|
|
2456
|
+
this.emit("started", this.state);
|
|
2457
|
+
}
|
|
2458
|
+
printWelcome() {
|
|
2459
|
+
console.log(chalk14.bold.blue("\n\u{1F6E1}\uFE0F Guardian Daemon Starting..."));
|
|
2460
|
+
console.log(chalk14.dim(`Project: ${this.config.projectId}`));
|
|
2461
|
+
console.log(chalk14.dim(`Watch Path: ${this.config.watchPath}`));
|
|
2462
|
+
console.log(chalk14.dim("\u2500".repeat(50)));
|
|
2463
|
+
}
|
|
2464
|
+
printActive() {
|
|
2465
|
+
console.log(chalk14.dim("\u2500".repeat(50)));
|
|
2466
|
+
console.log(chalk14.green.bold("\u2705 Guardian Daemon is now active"));
|
|
2467
|
+
console.log(chalk14.dim("Press Ctrl+C to stop\n"));
|
|
2468
|
+
}
|
|
2469
|
+
async syncHeuristics() {
|
|
2470
|
+
if (!this.heuristicEngine) return;
|
|
2471
|
+
const synced = await this.heuristicEngine.refreshRules(this.config.projectId, this.config.apiUrl, this.config.apiKey);
|
|
2472
|
+
if (synced) console.log(chalk14.green(" \u2713 Synced heuristic rules"));
|
|
2473
|
+
}
|
|
2474
|
+
setupFileWatcher() {
|
|
2475
|
+
console.log(chalk14.dim("\u{1F4C2} Starting file watcher..."));
|
|
2476
|
+
this.fileWatcher = createFileWatcher(this.config.watchPath);
|
|
2477
|
+
this.fileWatcher.on("change", (path24) => this.handleFileChange(path24));
|
|
2478
|
+
this.fileWatcher.start();
|
|
2479
|
+
console.log(chalk14.green(" \u2713 File watcher active"));
|
|
2480
|
+
}
|
|
2481
|
+
async handleFileChange(filePath) {
|
|
2482
|
+
this.state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
|
|
2483
|
+
if (this.config.verbose) console.log(chalk14.dim(` \u{1F4DD} File changed: ${filePath}`));
|
|
2484
|
+
let lineCount = 0;
|
|
2485
|
+
try {
|
|
2486
|
+
const content = await fs16.readFile(filePath, "utf-8");
|
|
2487
|
+
lineCount = content.split("\n").length;
|
|
2488
|
+
} catch (e) {
|
|
2489
|
+
}
|
|
2490
|
+
if (this.heuristicEngine && this.interventionProtocol && this.guardianMonitor) {
|
|
2491
|
+
const matches = await this.heuristicEngine.analyzeFile(filePath, {
|
|
2492
|
+
lineCount,
|
|
2493
|
+
rules: this.guardianMonitor.getRules()
|
|
2494
|
+
});
|
|
2495
|
+
for (const match of matches) {
|
|
2496
|
+
console.log(chalk14.magenta(` \u{1F4A1} PREDICTIVE ACTIVATION: ${match.skillId}`));
|
|
2497
|
+
console.log(chalk14.dim(` Reason: ${match.reason}`));
|
|
2498
|
+
const decision = this.interventionProtocol.evaluateTrigger(match.skillId, match.confidence);
|
|
2499
|
+
this.interventionProtocol.enforce(decision);
|
|
2500
|
+
await jitProvisionSkill(match.skillId, this.config.apiUrl, this.config.apiKey, this.config.projectId, process.cwd());
|
|
2501
|
+
await trackSkillUsage(this.config.apiUrl, this.config.apiKey, this.config.projectId, match.skillId);
|
|
2502
|
+
this.emit("skill:suggestion", match);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
if (this.guardianMonitor) {
|
|
2506
|
+
if (this.interventionProtocol) this.interventionProtocol.clear(filePath);
|
|
2507
|
+
const result = await this.guardianMonitor.checkFile(filePath);
|
|
2508
|
+
this.state.filesChecked++;
|
|
2509
|
+
if (result.violations.length > 0) {
|
|
2510
|
+
this.state.violationsFound += result.violations.length;
|
|
2511
|
+
this.emit("violation", { file: filePath, violations: result.violations });
|
|
2512
|
+
for (const v of result.violations) {
|
|
2513
|
+
const color = v.severity === "critical" ? chalk14.red : v.severity === "warning" ? chalk14.yellow : chalk14.blue;
|
|
2514
|
+
console.log(color(` [${v.severity.toUpperCase()}] ${filePath}: ${v.message}`));
|
|
2515
|
+
if (this.interventionProtocol) {
|
|
2516
|
+
const decision = this.interventionProtocol.evaluateViolation(v.message, v.severity);
|
|
2517
|
+
this.interventionProtocol.enforce(decision);
|
|
2518
|
+
this.interventionProtocol.registerViolation(filePath, decision);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
async setupBridge() {
|
|
2525
|
+
console.log(chalk14.dim("\u{1F309} Connecting to Agent Bridge..."));
|
|
2526
|
+
this.bridgeListener = createBridgeListener(this.config.projectId, this.config.apiUrl, this.config.apiKey);
|
|
2527
|
+
this.bridgeListener.on("task", (task) => {
|
|
2528
|
+
this.state.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
|
|
2529
|
+
this.state.tasksProcessed++;
|
|
2530
|
+
console.log(chalk14.cyan(`
|
|
2531
|
+
\u{1F4E5} New task received: ${task.id}`));
|
|
2532
|
+
this.emit("task", task);
|
|
2533
|
+
});
|
|
2534
|
+
await this.bridgeListener.connect();
|
|
2535
|
+
console.log(chalk14.green(" \u2713 Agent Bridge connected"));
|
|
2536
|
+
}
|
|
2537
|
+
async stop() {
|
|
2538
|
+
if (!this.state.isRunning) return;
|
|
2539
|
+
console.log(chalk14.dim("\n\u{1F6D1} Stopping Guardian Daemon..."));
|
|
2540
|
+
if (this.fileWatcher) await this.fileWatcher.stop();
|
|
2541
|
+
if (this.bridgeListener) await this.bridgeListener.disconnect();
|
|
2542
|
+
this.state.isRunning = false;
|
|
2543
|
+
console.log(chalk14.green("\u2713 Daemon stopped."));
|
|
2544
|
+
this.emit("stopped", this.state);
|
|
2545
|
+
}
|
|
2546
|
+
getState() {
|
|
2547
|
+
return { ...this.state };
|
|
2548
|
+
}
|
|
2549
|
+
};
|
|
2550
|
+
|
|
2551
|
+
// src/daemon/factory.ts
|
|
2552
|
+
init_config();
|
|
2553
|
+
async function createDaemon(options) {
|
|
2554
|
+
const apiUrl = getApiUrl();
|
|
2555
|
+
let projectId = options.project;
|
|
2556
|
+
if (!projectId) {
|
|
2557
|
+
const manifest = await loadManifest();
|
|
2558
|
+
if (manifest) projectId = manifest.project_id;
|
|
2559
|
+
}
|
|
2560
|
+
if (!projectId) projectId = getProjectId();
|
|
2561
|
+
if (!projectId) {
|
|
2562
|
+
throw new Error('No project ID found. Run "rigstate link" or use --project <id>.');
|
|
2563
|
+
}
|
|
2564
|
+
const apiKey = getApiKey();
|
|
2565
|
+
if (!apiKey) {
|
|
2566
|
+
throw new Error('Not authenticated. Run "rigstate login" first.');
|
|
2567
|
+
}
|
|
2568
|
+
const config2 = {
|
|
2569
|
+
projectId,
|
|
2570
|
+
apiUrl,
|
|
2571
|
+
apiKey,
|
|
2572
|
+
watchPath: options.path || process.cwd(),
|
|
2573
|
+
checkOnChange: true,
|
|
2574
|
+
bridgeEnabled: !options.noBridge,
|
|
2575
|
+
verbose: !!options.verbose
|
|
2576
|
+
};
|
|
2577
|
+
return new GuardianDaemon(config2);
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// src/commands/daemon.ts
|
|
2581
|
+
var PID_FILE = ".rigstate/daemon.pid";
|
|
2582
|
+
var STATE_FILE = ".rigstate/daemon.state.json";
|
|
2583
|
+
function createDaemonCommand() {
|
|
2584
|
+
const daemon = new Command9("daemon").description("Start the Guardian daemon for continuous monitoring");
|
|
2585
|
+
daemon.argument("[action]", "Action: start (default) or status", "start").option("--project <id>", "Project ID (or use .rigstate manifest)").option("--path <path>", "Path to watch", ".").option("--no-bridge", "Disable Agent Bridge connection").option("--verbose", "Enable verbose output").action(async (action, options) => {
|
|
2586
|
+
if (action === "status") {
|
|
2587
|
+
await showStatus();
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
const spinner = ora6();
|
|
2591
|
+
try {
|
|
2592
|
+
if (await isRunning()) {
|
|
2593
|
+
console.log(chalk15.yellow("\u26A0 Another daemon instance may be running."));
|
|
2594
|
+
console.log(chalk15.dim(` Check ${PID_FILE} or run "rigstate daemon status"`));
|
|
2595
|
+
console.log(chalk15.dim(" Use Ctrl+C to stop the running daemon first.\n"));
|
|
2596
|
+
}
|
|
2597
|
+
spinner.start("Initializing Guardian Daemon...");
|
|
2598
|
+
const daemonInstance = await createDaemon({
|
|
2599
|
+
project: options.project,
|
|
2600
|
+
path: options.path,
|
|
2601
|
+
noBridge: options.bridge === false,
|
|
2602
|
+
verbose: options.verbose
|
|
2603
|
+
});
|
|
2604
|
+
spinner.stop();
|
|
2605
|
+
await writePidFile();
|
|
2606
|
+
process.on("SIGINT", async () => {
|
|
2607
|
+
console.log(chalk15.dim("\n\nShutting down..."));
|
|
2608
|
+
await daemonInstance.stop();
|
|
2609
|
+
await cleanupPidFile();
|
|
2610
|
+
process.exit(0);
|
|
2611
|
+
});
|
|
2612
|
+
process.on("SIGTERM", async () => {
|
|
2613
|
+
await daemonInstance.stop();
|
|
2614
|
+
await cleanupPidFile();
|
|
2615
|
+
process.exit(0);
|
|
2616
|
+
});
|
|
2617
|
+
const stateInterval = setInterval(async () => {
|
|
2618
|
+
await writeStateFile(daemonInstance.getState());
|
|
2619
|
+
}, 5e3);
|
|
2620
|
+
daemonInstance.on("stopped", () => {
|
|
2621
|
+
clearInterval(stateInterval);
|
|
2622
|
+
});
|
|
2623
|
+
await daemonInstance.start();
|
|
2624
|
+
await new Promise(() => {
|
|
2625
|
+
});
|
|
2626
|
+
} catch (error) {
|
|
2627
|
+
spinner.fail(chalk15.red("Failed to start daemon"));
|
|
2628
|
+
console.error(chalk15.red("Error:"), error.message);
|
|
2629
|
+
process.exit(1);
|
|
2630
|
+
}
|
|
2631
|
+
});
|
|
2632
|
+
return daemon;
|
|
2633
|
+
}
|
|
2634
|
+
async function isRunning() {
|
|
2635
|
+
try {
|
|
2636
|
+
const pidPath = path19.join(process.cwd(), PID_FILE);
|
|
2637
|
+
const content = await fs17.readFile(pidPath, "utf-8");
|
|
2638
|
+
const pid = parseInt(content.trim(), 10);
|
|
2639
|
+
try {
|
|
2640
|
+
process.kill(pid, 0);
|
|
2641
|
+
return true;
|
|
2642
|
+
} catch {
|
|
2643
|
+
await fs17.unlink(pidPath);
|
|
2644
|
+
return false;
|
|
2645
|
+
}
|
|
2646
|
+
} catch {
|
|
2647
|
+
return false;
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
async function writePidFile() {
|
|
2651
|
+
try {
|
|
2652
|
+
const dir = path19.join(process.cwd(), ".rigstate");
|
|
2653
|
+
await fs17.mkdir(dir, { recursive: true });
|
|
2654
|
+
await fs17.writeFile(path19.join(dir, "daemon.pid"), process.pid.toString());
|
|
2655
|
+
} catch {
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
async function cleanupPidFile() {
|
|
2659
|
+
try {
|
|
2660
|
+
await fs17.unlink(path19.join(process.cwd(), PID_FILE));
|
|
2661
|
+
await fs17.unlink(path19.join(process.cwd(), STATE_FILE));
|
|
2662
|
+
} catch {
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
async function writeStateFile(state) {
|
|
2666
|
+
try {
|
|
2667
|
+
const dir = path19.join(process.cwd(), ".rigstate");
|
|
2668
|
+
await fs17.mkdir(dir, { recursive: true });
|
|
2669
|
+
await fs17.writeFile(
|
|
2670
|
+
path19.join(dir, "daemon.state.json"),
|
|
2671
|
+
JSON.stringify(state, null, 2)
|
|
2672
|
+
);
|
|
2673
|
+
} catch {
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
async function showStatus() {
|
|
2677
|
+
console.log(chalk15.bold("\n\u{1F6E1}\uFE0F Guardian Daemon Status\n"));
|
|
2678
|
+
const running = await isRunning();
|
|
2679
|
+
if (!running) {
|
|
2680
|
+
console.log(chalk15.yellow("Status: Not running"));
|
|
2681
|
+
console.log(chalk15.dim('Use "rigstate daemon" to start.\n'));
|
|
2682
|
+
return;
|
|
2683
|
+
}
|
|
2684
|
+
console.log(chalk15.green("Status: Running"));
|
|
2685
|
+
try {
|
|
2686
|
+
const statePath = path19.join(process.cwd(), STATE_FILE);
|
|
2687
|
+
const content = await fs17.readFile(statePath, "utf-8");
|
|
2688
|
+
const state = JSON.parse(content);
|
|
2689
|
+
console.log(chalk15.dim("\u2500".repeat(40)));
|
|
2690
|
+
console.log(`Started at: ${state.startedAt || "Unknown"}`);
|
|
2691
|
+
console.log(`Files checked: ${state.filesChecked || 0}`);
|
|
2692
|
+
console.log(`Violations: ${state.violationsFound || 0}`);
|
|
2693
|
+
console.log(`Tasks processed: ${state.tasksProcessed || 0}`);
|
|
2694
|
+
console.log(`Last activity: ${state.lastActivity || "None"}`);
|
|
2695
|
+
console.log(chalk15.dim("\u2500".repeat(40)));
|
|
2696
|
+
} catch {
|
|
2697
|
+
console.log(chalk15.dim("(State file not found)"));
|
|
2698
|
+
}
|
|
2699
|
+
try {
|
|
2700
|
+
const pidPath = path19.join(process.cwd(), PID_FILE);
|
|
2701
|
+
const pid = await fs17.readFile(pidPath, "utf-8");
|
|
2702
|
+
console.log(chalk15.dim(`PID: ${pid.trim()}`));
|
|
2703
|
+
} catch {
|
|
2704
|
+
}
|
|
2705
|
+
console.log("");
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// src/commands/work.ts
|
|
2709
|
+
init_esm_shims();
|
|
2710
|
+
init_config();
|
|
2711
|
+
import { Command as Command10 } from "commander";
|
|
2712
|
+
import chalk16 from "chalk";
|
|
2713
|
+
import ora7 from "ora";
|
|
2714
|
+
import axios12 from "axios";
|
|
2715
|
+
import inquirer2 from "inquirer";
|
|
2716
|
+
import fs18 from "fs/promises";
|
|
2717
|
+
function createWorkCommand() {
|
|
2718
|
+
return new Command10("work").alias("start").description("Select and execute a Roadmap Task (fetches IDE Prompt)").argument("[taskId]", "Optional Task ID (e.g., T-1021) to start immediately").option("--project <id>", "Project ID").action(async (taskId, options) => {
|
|
2719
|
+
const spinner = ora7();
|
|
2720
|
+
try {
|
|
2721
|
+
const apiKey = getApiKey();
|
|
2722
|
+
const apiUrl = getApiUrl();
|
|
2723
|
+
const projectId = options.project || getProjectId();
|
|
2724
|
+
if (!projectId) {
|
|
2725
|
+
console.log(chalk16.red("\u274C Project ID is required. Run `rigstate link` or pass --project <id>"));
|
|
2726
|
+
process.exit(1);
|
|
2727
|
+
}
|
|
2728
|
+
if (!taskId) {
|
|
2729
|
+
spinner.start("Fetching active roadmap tasks...");
|
|
2730
|
+
}
|
|
2731
|
+
const response = await axios12.get(
|
|
2732
|
+
`${apiUrl}/api/v1/roadmap?project_id=${projectId}`,
|
|
2733
|
+
{ headers: { "Authorization": `Bearer ${apiKey}` }, timeout: 1e4 }
|
|
2734
|
+
);
|
|
2735
|
+
if (!response.data.success) {
|
|
2736
|
+
throw new Error(response.data.error || "Failed to fetch roadmap");
|
|
2737
|
+
}
|
|
2738
|
+
const allTasks = response.data.data.roadmap || [];
|
|
2739
|
+
const actionableTasks = allTasks.filter((t) => ["ACTIVE", "LOCKED"].includes(t.status)).sort((a, b) => {
|
|
2740
|
+
if (a.status === "ACTIVE" && b.status !== "ACTIVE") return -1;
|
|
2741
|
+
if (b.status === "ACTIVE" && a.status !== "ACTIVE") return 1;
|
|
2742
|
+
return a.step_number - b.step_number;
|
|
2743
|
+
});
|
|
2744
|
+
spinner.stop();
|
|
2745
|
+
let selectedTask;
|
|
2746
|
+
if (taskId) {
|
|
2747
|
+
selectedTask = allTasks.find(
|
|
2748
|
+
(t) => t.id === taskId || `T-${t.step_number}` === taskId || t.step_number.toString() === taskId
|
|
2749
|
+
);
|
|
2750
|
+
if (!selectedTask) {
|
|
2751
|
+
console.log(chalk16.red(`\u274C Task '${taskId}' not found in roadmap.`));
|
|
2752
|
+
return;
|
|
2753
|
+
}
|
|
2754
|
+
} else {
|
|
2755
|
+
if (actionableTasks.length === 0) {
|
|
2756
|
+
console.log(chalk16.yellow("No active or locked tasks found. The Roadmap is clear! \u{1F389}"));
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
const choices = actionableTasks.map((t) => {
|
|
2760
|
+
const id = `T-${t.step_number}`;
|
|
2761
|
+
const statusIcon = t.status === "ACTIVE" ? "\u25B6\uFE0F" : "\u{1F512}";
|
|
2762
|
+
const priority = t.priority === "MVP" ? chalk16.magenta("[MVP]") : chalk16.blue(`[${t.priority}]`);
|
|
2763
|
+
return {
|
|
2764
|
+
name: `${statusIcon} ${chalk16.bold(id)}: ${t.title} ${priority}`,
|
|
2765
|
+
value: t,
|
|
2766
|
+
short: `${id}: ${t.title}`
|
|
2767
|
+
};
|
|
2768
|
+
});
|
|
2769
|
+
const answer = await inquirer2.prompt([{
|
|
2770
|
+
type: "list",
|
|
2771
|
+
name: "task",
|
|
2772
|
+
message: "Which task are you working on?",
|
|
2773
|
+
choices,
|
|
2774
|
+
pageSize: 15
|
|
2775
|
+
}]);
|
|
2776
|
+
selectedTask = answer.task;
|
|
2777
|
+
}
|
|
2778
|
+
console.log("\n" + chalk16.bold.underline(`\u{1F680} WORK MODE: ${selectedTask.title}`));
|
|
2779
|
+
console.log(chalk16.dim(`ID: T-${selectedTask.step_number} | Status: ${selectedTask.status}`));
|
|
2780
|
+
if (selectedTask.prompt_content) {
|
|
2781
|
+
console.log(chalk16.yellow.bold("\n\u{1F4CB} IDE EXECUTION SIGNAL (Prompt):"));
|
|
2782
|
+
console.log(chalk16.gray("--------------------------------------------------"));
|
|
2783
|
+
console.log(selectedTask.prompt_content);
|
|
2784
|
+
console.log(chalk16.gray("--------------------------------------------------"));
|
|
2785
|
+
const { action } = await inquirer2.prompt([{
|
|
2786
|
+
type: "list",
|
|
2787
|
+
name: "action",
|
|
2788
|
+
message: "What do you want to do?",
|
|
2789
|
+
choices: [
|
|
2790
|
+
{ name: "Copy Prompt (Print clean)", value: "print" },
|
|
2791
|
+
{ name: "Create .cursorrules (Agent Context)", value: "cursorrules" },
|
|
2792
|
+
{ name: "Mark as ACTIVE (if LOCKED)", value: "activate" },
|
|
2793
|
+
{ name: "Mark as COMPLETED", value: "complete" },
|
|
2794
|
+
{ name: "Cancel", value: "cancel" }
|
|
2795
|
+
]
|
|
2796
|
+
}]);
|
|
2797
|
+
if (action === "cursorrules") {
|
|
2798
|
+
await fs18.writeFile(".rigstate-prompt.md", selectedTask.prompt_content);
|
|
2799
|
+
console.log(chalk16.green(`\u2705 Prompt saved to ${chalk16.bold(".rigstate-prompt.md")}`));
|
|
2800
|
+
console.log(chalk16.dim("You can now reference this file in your IDE chat (@.rigstate-prompt.md)"));
|
|
2801
|
+
} else if (action === "print") {
|
|
2802
|
+
console.log("\n" + selectedTask.prompt_content + "\n");
|
|
2803
|
+
} else if (action === "activate" && selectedTask.status !== "ACTIVE") {
|
|
2804
|
+
try {
|
|
2805
|
+
await axios12.post(
|
|
2806
|
+
`${apiUrl}/api/v1/roadmap/update-status`,
|
|
2807
|
+
{ step_id: selectedTask.id, status: "ACTIVE", project_id: projectId },
|
|
2808
|
+
{ headers: { "Authorization": `Bearer ${apiKey}` } }
|
|
2809
|
+
);
|
|
2810
|
+
console.log(chalk16.green(`\u2705 Task marked as ACTIVE.`));
|
|
2811
|
+
} catch (e) {
|
|
2812
|
+
console.error(chalk16.red(`Failed to update status: ${e.message}`));
|
|
2813
|
+
}
|
|
2814
|
+
} else if (action === "complete") {
|
|
2815
|
+
try {
|
|
2816
|
+
await axios12.post(
|
|
2817
|
+
`${apiUrl}/api/v1/roadmap/update-status`,
|
|
2818
|
+
{ step_id: selectedTask.id, status: "COMPLETED", project_id: projectId },
|
|
2819
|
+
{ headers: { "Authorization": `Bearer ${apiKey}` } }
|
|
2820
|
+
);
|
|
2821
|
+
console.log(chalk16.green(`\u2705 Task marked as COMPLETED. Great job!`));
|
|
2822
|
+
} catch (e) {
|
|
2823
|
+
console.error(chalk16.red(`Failed to update status: ${e.message}`));
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
} else {
|
|
2827
|
+
console.log(chalk16.yellow("\n\u26A0\uFE0F No specific IDE Prompt found for this task (Legacy Task?)."));
|
|
2828
|
+
console.log(chalk16.dim("Objective: " + (selectedTask.summary || selectedTask.description || "Check web UI for details.")));
|
|
2829
|
+
}
|
|
2830
|
+
} catch (error) {
|
|
2831
|
+
spinner.stop();
|
|
2832
|
+
console.error(chalk16.red(`
|
|
2833
|
+
Command failed: ${error.message}`));
|
|
2834
|
+
}
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
// src/commands/watch.ts
|
|
2839
|
+
init_esm_shims();
|
|
2840
|
+
init_config();
|
|
2841
|
+
import { Command as Command11 } from "commander";
|
|
2842
|
+
import chalk17 from "chalk";
|
|
2843
|
+
import ora8 from "ora";
|
|
2844
|
+
import chokidar2 from "chokidar";
|
|
2845
|
+
import fs19 from "fs/promises";
|
|
2846
|
+
import path20 from "path";
|
|
2847
|
+
import { execSync as execSync3 } from "child_process";
|
|
2848
|
+
import axios13 from "axios";
|
|
2849
|
+
function createWatchCommand() {
|
|
2850
|
+
const watch2 = new Command11("watch");
|
|
2851
|
+
watch2.description("Watch for changes and auto-verify roadmap tasks").option("--no-auto-commit", "Disable auto-commit on verification").option("--no-auto-push", "Disable auto-push after commit").option("--run-tests", "Run tests before committing").option("--test-command <cmd>", "Custom test command (default: npm test)").action(async (options) => {
|
|
2852
|
+
console.log(chalk17.bold.blue("\u{1F52D} Rigstate Watch Mode"));
|
|
2853
|
+
console.log(chalk17.dim("Monitoring for task completion..."));
|
|
2854
|
+
console.log("");
|
|
2855
|
+
let apiKey;
|
|
2856
|
+
let projectId;
|
|
2857
|
+
try {
|
|
2858
|
+
apiKey = getApiKey();
|
|
2859
|
+
} catch (e) {
|
|
2860
|
+
console.log(chalk17.red('Not authenticated. Run "rigstate login" first.'));
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
projectId = getProjectId();
|
|
2864
|
+
if (!projectId) {
|
|
2865
|
+
try {
|
|
2866
|
+
const manifestPath = path20.join(process.cwd(), ".rigstate");
|
|
2867
|
+
const content = await fs19.readFile(manifestPath, "utf-8");
|
|
2868
|
+
const manifest = JSON.parse(content);
|
|
2869
|
+
projectId = manifest.project_id;
|
|
2870
|
+
} catch (e) {
|
|
2871
|
+
}
|
|
2872
|
+
}
|
|
2873
|
+
if (!projectId) {
|
|
2874
|
+
console.log(chalk17.red('No project context. Run "rigstate link" or "rigstate sync --project <id>" first.'));
|
|
2875
|
+
return;
|
|
2876
|
+
}
|
|
2877
|
+
const apiUrl = getApiUrl();
|
|
2878
|
+
const config2 = {
|
|
2879
|
+
autoCommit: options.autoCommit !== false,
|
|
2880
|
+
autoPush: options.autoPush !== false,
|
|
2881
|
+
runTests: options.runTests || false,
|
|
2882
|
+
testCommand: options.testCommand || "npm test"
|
|
2883
|
+
};
|
|
2884
|
+
console.log(chalk17.dim(`Auto-commit: ${config2.autoCommit ? "ON" : "OFF"}`));
|
|
2885
|
+
console.log(chalk17.dim(`Auto-push: ${config2.autoPush ? "ON" : "OFF"}`));
|
|
2886
|
+
console.log("");
|
|
2887
|
+
const fetchActiveTask = async () => {
|
|
2888
|
+
try {
|
|
2889
|
+
const response = await axios13.get(`${apiUrl}/api/v1/roadmap`, {
|
|
2890
|
+
params: { project_id: projectId },
|
|
2891
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
2892
|
+
});
|
|
2893
|
+
if (!response.data.success) return null;
|
|
2894
|
+
const roadmap = response.data.data.roadmap || [];
|
|
2895
|
+
const statusPriority = {
|
|
2896
|
+
"IN_PROGRESS": 0,
|
|
2897
|
+
"ACTIVE": 1,
|
|
2898
|
+
"LOCKED": 2
|
|
2899
|
+
};
|
|
2900
|
+
const activeTasks = roadmap.filter((t) => ["IN_PROGRESS", "ACTIVE", "LOCKED"].includes(t.status)).sort((a, b) => {
|
|
2901
|
+
const pA = statusPriority[a.status] ?? 99;
|
|
2902
|
+
const pB = statusPriority[b.status] ?? 99;
|
|
2903
|
+
if (pA !== pB) return pA - pB;
|
|
2904
|
+
return (a.step_number || 0) - (b.step_number || 0);
|
|
2905
|
+
});
|
|
2906
|
+
return activeTasks[0] || null;
|
|
2907
|
+
} catch (e) {
|
|
2908
|
+
return null;
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
const checkCriteria = async (criteria) => {
|
|
2912
|
+
try {
|
|
2913
|
+
const fullPath = path20.resolve(process.cwd(), criteria.path);
|
|
2914
|
+
switch (criteria.type) {
|
|
2915
|
+
case "file_exists":
|
|
2916
|
+
await fs19.access(fullPath);
|
|
2917
|
+
return true;
|
|
2918
|
+
case "file_content":
|
|
2919
|
+
const content = await fs19.readFile(fullPath, "utf-8");
|
|
2920
|
+
return content.length > 0;
|
|
2921
|
+
case "content_match":
|
|
2922
|
+
if (!criteria.match) return false;
|
|
2923
|
+
const fileContent = await fs19.readFile(fullPath, "utf-8");
|
|
2924
|
+
return fileContent.includes(criteria.match);
|
|
2925
|
+
default:
|
|
2926
|
+
return false;
|
|
2927
|
+
}
|
|
2928
|
+
} catch (e) {
|
|
2929
|
+
return false;
|
|
2930
|
+
}
|
|
2931
|
+
};
|
|
2932
|
+
const completeTask = async (taskId, task) => {
|
|
2933
|
+
const spinner = ora8("Completing task...").start();
|
|
2934
|
+
try {
|
|
2935
|
+
if (config2.runTests) {
|
|
2936
|
+
spinner.text = "Running tests...";
|
|
2937
|
+
try {
|
|
2938
|
+
execSync3(config2.testCommand, { stdio: "pipe" });
|
|
2939
|
+
spinner.text = "Tests passed!";
|
|
2940
|
+
} catch (e) {
|
|
2941
|
+
spinner.fail("Tests failed. Task not completed.");
|
|
2942
|
+
return;
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
await axios13.post(`${apiUrl}/api/v1/roadmap/update-status`, {
|
|
2946
|
+
project_id: projectId,
|
|
2947
|
+
chunk_id: taskId,
|
|
2948
|
+
status: "COMPLETED"
|
|
2949
|
+
}, {
|
|
2950
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
2951
|
+
});
|
|
2952
|
+
spinner.succeed(chalk17.green(`\u2705 Task #${task.step_number} completed: ${task.title}`));
|
|
2953
|
+
if (config2.autoCommit) {
|
|
2954
|
+
spinner.start("Committing changes...");
|
|
2955
|
+
try {
|
|
2956
|
+
execSync3("git add -A", { stdio: "pipe" });
|
|
2957
|
+
const commitMsg = `feat: Complete task #${task.step_number} - ${task.title}`;
|
|
2958
|
+
execSync3(`git commit -m "${commitMsg}"`, { stdio: "pipe" });
|
|
2959
|
+
spinner.succeed("Changes committed");
|
|
2960
|
+
if (config2.autoPush) {
|
|
2961
|
+
spinner.start("Pushing to remote...");
|
|
2962
|
+
try {
|
|
2963
|
+
execSync3("git push", { stdio: "pipe" });
|
|
2964
|
+
spinner.succeed("Pushed to remote");
|
|
2965
|
+
} catch (e) {
|
|
2966
|
+
spinner.warn("Push failed (no remote or conflict)");
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
} catch (e) {
|
|
2970
|
+
spinner.warn("Nothing to commit or commit failed");
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
console.log("");
|
|
2974
|
+
console.log(chalk17.blue("Watching for next task..."));
|
|
2975
|
+
} catch (e) {
|
|
2976
|
+
spinner.fail(`Failed to complete task: ${e.message}`);
|
|
2977
|
+
}
|
|
2978
|
+
};
|
|
2979
|
+
let currentTask = null;
|
|
2980
|
+
let isProcessing = false;
|
|
2981
|
+
const processActiveTask = async () => {
|
|
2982
|
+
if (isProcessing) return;
|
|
2983
|
+
isProcessing = true;
|
|
2984
|
+
const task = await fetchActiveTask();
|
|
2985
|
+
if (!task) {
|
|
2986
|
+
if (currentTask) {
|
|
2987
|
+
console.log(chalk17.green("\u{1F389} All tasks completed! Watching for new tasks..."));
|
|
2988
|
+
currentTask = null;
|
|
2989
|
+
}
|
|
2990
|
+
isProcessing = false;
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2993
|
+
if (!currentTask || currentTask.id !== task.id) {
|
|
2994
|
+
currentTask = task;
|
|
2995
|
+
console.log("");
|
|
2996
|
+
console.log(chalk17.bold.yellow(`\u{1F4CC} Active Task #${task.step_number}: ${task.title}`));
|
|
2997
|
+
console.log(chalk17.dim(`Status: ${task.status}`));
|
|
2998
|
+
if (task.verification_criteria) {
|
|
2999
|
+
console.log(chalk17.dim("Verification: Auto-checking criteria..."));
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
if (task.verification_criteria && Array.isArray(task.verification_criteria)) {
|
|
3003
|
+
let allPassed = true;
|
|
3004
|
+
for (const criteria of task.verification_criteria) {
|
|
3005
|
+
const passed = await checkCriteria(criteria);
|
|
3006
|
+
if (!passed) {
|
|
3007
|
+
allPassed = false;
|
|
3008
|
+
break;
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
if (allPassed) {
|
|
3012
|
+
console.log(chalk17.green("\u2713 All verification criteria passed!"));
|
|
3013
|
+
await completeTask(task.id, task);
|
|
3014
|
+
currentTask = null;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
isProcessing = false;
|
|
3018
|
+
};
|
|
3019
|
+
await processActiveTask();
|
|
3020
|
+
const watcher = chokidar2.watch(".", {
|
|
3021
|
+
ignored: [
|
|
3022
|
+
/(^|[\/\\])\../,
|
|
3023
|
+
// dotfiles
|
|
3024
|
+
"**/node_modules/**",
|
|
3025
|
+
"**/.git/**",
|
|
3026
|
+
"**/.next/**",
|
|
3027
|
+
"**/dist/**"
|
|
3028
|
+
],
|
|
3029
|
+
persistent: true,
|
|
3030
|
+
ignoreInitial: true
|
|
3031
|
+
});
|
|
3032
|
+
watcher.on("all", async (event, filePath) => {
|
|
3033
|
+
if (["add", "change", "unlink"].includes(event)) {
|
|
3034
|
+
setTimeout(() => processActiveTask(), 500);
|
|
3035
|
+
}
|
|
3036
|
+
});
|
|
3037
|
+
console.log(chalk17.dim("Watching for file changes... (Ctrl+C to exit)"));
|
|
3038
|
+
setInterval(() => processActiveTask(), 3e4);
|
|
3039
|
+
process.on("SIGINT", () => {
|
|
3040
|
+
console.log("");
|
|
3041
|
+
console.log(chalk17.dim("Watch mode stopped."));
|
|
3042
|
+
watcher.close();
|
|
3043
|
+
process.exit(0);
|
|
3044
|
+
});
|
|
3045
|
+
});
|
|
3046
|
+
return watch2;
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
// src/commands/focus.ts
|
|
3050
|
+
init_esm_shims();
|
|
3051
|
+
init_config();
|
|
3052
|
+
import { Command as Command12 } from "commander";
|
|
3053
|
+
import chalk18 from "chalk";
|
|
3054
|
+
import ora9 from "ora";
|
|
3055
|
+
import axios14 from "axios";
|
|
3056
|
+
import { execSync as execSync4 } from "child_process";
|
|
3057
|
+
import fs20 from "fs/promises";
|
|
3058
|
+
import path21 from "path";
|
|
3059
|
+
function createFocusCommand() {
|
|
3060
|
+
const focus = new Command12("focus");
|
|
3061
|
+
focus.alias("task").description("Get the next active roadmap task and copy its prompt to clipboard").option("--no-copy", "Do not copy to clipboard").action(async (options) => {
|
|
3062
|
+
const spinner = ora9("Fetching next objective...").start();
|
|
3063
|
+
let apiKey;
|
|
3064
|
+
let projectId;
|
|
3065
|
+
try {
|
|
3066
|
+
apiKey = getApiKey();
|
|
3067
|
+
} catch (e) {
|
|
3068
|
+
spinner.fail(chalk18.red('Not authenticated. Run "rigstate login" first.'));
|
|
3069
|
+
return;
|
|
3070
|
+
}
|
|
3071
|
+
projectId = getProjectId();
|
|
3072
|
+
if (!projectId) {
|
|
3073
|
+
try {
|
|
3074
|
+
const manifestPath = path21.join(process.cwd(), ".rigstate");
|
|
3075
|
+
const content = await fs20.readFile(manifestPath, "utf-8");
|
|
3076
|
+
const manifest = JSON.parse(content);
|
|
3077
|
+
projectId = manifest.project_id;
|
|
3078
|
+
} catch (e) {
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
if (!projectId) {
|
|
3082
|
+
spinner.fail(chalk18.red('No project context. Run "rigstate link" first.'));
|
|
3083
|
+
return;
|
|
3084
|
+
}
|
|
3085
|
+
const apiUrl = getApiUrl();
|
|
3086
|
+
try {
|
|
3087
|
+
const response = await axios14.get(`${apiUrl}/api/v1/roadmap`, {
|
|
3088
|
+
params: { project_id: projectId },
|
|
3089
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
3090
|
+
});
|
|
3091
|
+
if (!response.data.success) {
|
|
3092
|
+
throw new Error(response.data.error || "Failed to fetch roadmap");
|
|
3093
|
+
}
|
|
3094
|
+
const roadmap = response.data.data.roadmap || [];
|
|
3095
|
+
const statusPriority = {
|
|
3096
|
+
"IN_PROGRESS": 0,
|
|
3097
|
+
"ACTIVE": 1,
|
|
3098
|
+
"LOCKED": 2
|
|
3099
|
+
};
|
|
3100
|
+
const activeTasks = roadmap.filter((t) => ["IN_PROGRESS", "ACTIVE", "LOCKED"].includes(t.status)).sort((a, b) => {
|
|
3101
|
+
const pA = statusPriority[a.status] ?? 99;
|
|
3102
|
+
const pB = statusPriority[b.status] ?? 99;
|
|
3103
|
+
if (pA !== pB) return pA - pB;
|
|
3104
|
+
return (a.step_number || 0) - (b.step_number || 0);
|
|
3105
|
+
});
|
|
3106
|
+
if (activeTasks.length === 0) {
|
|
3107
|
+
spinner.succeed("All caught up! No active tasks found.");
|
|
3108
|
+
return;
|
|
3109
|
+
}
|
|
3110
|
+
const nextTask = activeTasks[0];
|
|
3111
|
+
spinner.stop();
|
|
3112
|
+
console.log("");
|
|
3113
|
+
console.log(chalk18.bold.blue(`\u{1F4CC} Task #${nextTask.step_number || "?"}: ${nextTask.title}`));
|
|
3114
|
+
const statusColor = nextTask.status === "IN_PROGRESS" ? chalk18.yellow : nextTask.status === "ACTIVE" ? chalk18.green : chalk18.dim;
|
|
3115
|
+
console.log(chalk18.dim("Status: ") + statusColor(nextTask.status));
|
|
3116
|
+
console.log(chalk18.dim("\u2500".repeat(60)));
|
|
3117
|
+
if (nextTask.prompt_content) {
|
|
3118
|
+
console.log(chalk18.white(nextTask.prompt_content));
|
|
3119
|
+
console.log(chalk18.dim("\u2500".repeat(60)));
|
|
3120
|
+
if (options.copy !== false) {
|
|
3121
|
+
try {
|
|
3122
|
+
if (process.platform === "darwin") {
|
|
3123
|
+
execSync4("pbcopy", { input: nextTask.prompt_content });
|
|
3124
|
+
console.log(chalk18.green("\u2705 Prompt copied to clipboard! Ready to paste (Cmd+V)."));
|
|
3125
|
+
} else if (process.platform === "linux") {
|
|
3126
|
+
try {
|
|
3127
|
+
execSync4("xclip -selection clipboard", { input: nextTask.prompt_content });
|
|
3128
|
+
console.log(chalk18.green("\u2705 Prompt copied to clipboard!"));
|
|
3129
|
+
} catch (e) {
|
|
3130
|
+
console.log(chalk18.yellow("\u2139\uFE0F Copy prompt manually (xclip not available)"));
|
|
3131
|
+
}
|
|
3132
|
+
} else {
|
|
3133
|
+
console.log(chalk18.yellow("\u2139\uFE0F Copy prompt manually (Auto-copy not supported on this OS)"));
|
|
3134
|
+
}
|
|
3135
|
+
} catch (e) {
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
} else {
|
|
3139
|
+
console.log(chalk18.yellow("No prompt instructions available."));
|
|
3140
|
+
if (nextTask.architectural_brief) {
|
|
3141
|
+
console.log(chalk18.bold("Brief:"));
|
|
3142
|
+
console.log(nextTask.architectural_brief);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
console.log("");
|
|
3146
|
+
} catch (e) {
|
|
3147
|
+
spinner.fail(chalk18.red(`Failed to fetch task: ${e.message}`));
|
|
3148
|
+
}
|
|
3149
|
+
});
|
|
3150
|
+
return focus;
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
// src/commands/env.ts
|
|
3154
|
+
init_esm_shims();
|
|
3155
|
+
init_config();
|
|
3156
|
+
import { Command as Command13 } from "commander";
|
|
3157
|
+
import chalk19 from "chalk";
|
|
3158
|
+
import ora10 from "ora";
|
|
3159
|
+
import fs21 from "fs/promises";
|
|
3160
|
+
import path22 from "path";
|
|
3161
|
+
import axios15 from "axios";
|
|
3162
|
+
function createEnvPullCommand() {
|
|
3163
|
+
const envPull = new Command13("env");
|
|
3164
|
+
envPull.command("pull").description("Pull environment variables from project vault").action(async () => {
|
|
3165
|
+
console.log("");
|
|
3166
|
+
console.log(chalk19.bold.yellow("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
3167
|
+
console.log(chalk19.bold.yellow("\u2551") + chalk19.bold.white(" \u{1F6E1}\uFE0F RIGSTATE SOVEREIGN VAULT SYNC \u{1F6E1}\uFE0F ") + chalk19.bold.yellow("\u2551"));
|
|
3168
|
+
console.log(chalk19.bold.yellow("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
3169
|
+
console.log("");
|
|
3170
|
+
const spinner = ora10("Authenticating with Vault...").start();
|
|
3171
|
+
let apiKey;
|
|
3172
|
+
let projectId;
|
|
3173
|
+
try {
|
|
3174
|
+
apiKey = getApiKey();
|
|
3175
|
+
} catch (e) {
|
|
3176
|
+
spinner.fail(chalk19.red('Not authenticated. Run "rigstate login" first.'));
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3179
|
+
spinner.succeed("Authenticated");
|
|
3180
|
+
spinner.start("Reading project configuration...");
|
|
3181
|
+
projectId = getProjectId();
|
|
3182
|
+
if (!projectId) {
|
|
3183
|
+
try {
|
|
3184
|
+
const manifestPath = path22.join(process.cwd(), ".rigstate");
|
|
3185
|
+
const content = await fs21.readFile(manifestPath, "utf-8");
|
|
3186
|
+
const manifest = JSON.parse(content);
|
|
3187
|
+
projectId = manifest.project_id;
|
|
3188
|
+
} catch (e) {
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
if (!projectId) {
|
|
3192
|
+
spinner.fail(chalk19.red('No project context. Run "rigstate link" first.'));
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
spinner.succeed(`Project: ${chalk19.cyan(projectId.substring(0, 8))}...`);
|
|
3196
|
+
const apiUrl = getApiUrl();
|
|
3197
|
+
spinner.start("Fetching secrets from Vault...");
|
|
3198
|
+
try {
|
|
3199
|
+
const response = await axios15.post(`${apiUrl}/api/v1/vault/sync`, {
|
|
3200
|
+
project_id: projectId
|
|
3201
|
+
}, {
|
|
3202
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
3203
|
+
});
|
|
3204
|
+
if (!response.data.success) {
|
|
3205
|
+
throw new Error(response.data.error || "Failed to fetch secrets");
|
|
3206
|
+
}
|
|
3207
|
+
const vaultContent = response.data.data.content || "";
|
|
3208
|
+
const secretCount = response.data.data.count || 0;
|
|
3209
|
+
if (secretCount === 0) {
|
|
3210
|
+
spinner.info("No secrets found in Vault for this project.");
|
|
3211
|
+
console.log(chalk19.dim(" Add secrets via the Rigstate web interface."));
|
|
3212
|
+
return;
|
|
3213
|
+
}
|
|
3214
|
+
spinner.succeed(`Retrieved ${chalk19.bold(secretCount)} secret(s)`);
|
|
3215
|
+
const envFile = path22.resolve(process.cwd(), ".env.local");
|
|
3216
|
+
let existingContent = "";
|
|
3217
|
+
let existingKeys = /* @__PURE__ */ new Set();
|
|
3218
|
+
try {
|
|
3219
|
+
existingContent = await fs21.readFile(envFile, "utf-8");
|
|
3220
|
+
existingContent.split("\n").forEach((line) => {
|
|
3221
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
|
|
3222
|
+
if (match) existingKeys.add(match[1]);
|
|
3223
|
+
});
|
|
3224
|
+
} catch (e) {
|
|
3225
|
+
}
|
|
3226
|
+
const vaultKeys = /* @__PURE__ */ new Set();
|
|
3227
|
+
vaultContent.split("\n").forEach((line) => {
|
|
3228
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=/);
|
|
3229
|
+
if (match) vaultKeys.add(match[1]);
|
|
3230
|
+
});
|
|
3231
|
+
let newCount = 0;
|
|
3232
|
+
let updatedCount = 0;
|
|
3233
|
+
vaultKeys.forEach((key) => {
|
|
3234
|
+
if (!existingKeys.has(key)) {
|
|
3235
|
+
newCount++;
|
|
3236
|
+
} else {
|
|
3237
|
+
updatedCount++;
|
|
3238
|
+
}
|
|
3239
|
+
});
|
|
3240
|
+
const unchangedCount = existingKeys.size - updatedCount;
|
|
3241
|
+
spinner.start("Writing .env.local...");
|
|
3242
|
+
const header = [
|
|
3243
|
+
"# ==========================================",
|
|
3244
|
+
"# RIGSTATE SOVEREIGN FOUNDATION",
|
|
3245
|
+
"# Authenticated Environment Configuration",
|
|
3246
|
+
`# Synced at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
3247
|
+
`# Project: ${projectId}`,
|
|
3248
|
+
"# ==========================================",
|
|
3249
|
+
""
|
|
3250
|
+
].join("\n");
|
|
3251
|
+
await fs21.writeFile(envFile, header + vaultContent + "\n");
|
|
3252
|
+
spinner.succeed("Written to .env.local");
|
|
3253
|
+
console.log("");
|
|
3254
|
+
console.log(chalk19.bold.green("\u2705 Environment synchronized successfully"));
|
|
3255
|
+
console.log("");
|
|
3256
|
+
console.log(chalk19.dim(" Summary:"));
|
|
3257
|
+
console.log(chalk19.green(` + ${newCount} new`));
|
|
3258
|
+
console.log(chalk19.yellow(` ~ ${updatedCount} updated`));
|
|
3259
|
+
console.log(chalk19.dim(` = ${unchangedCount} unchanged`));
|
|
3260
|
+
console.log("");
|
|
3261
|
+
console.log(chalk19.bold.yellow("\u26A0\uFE0F Security Reminder:"));
|
|
3262
|
+
console.log(chalk19.dim(" - Never commit .env.local to version control."));
|
|
3263
|
+
console.log(chalk19.dim(" - Ensure .gitignore includes .env.local"));
|
|
3264
|
+
console.log("");
|
|
3265
|
+
} catch (e) {
|
|
3266
|
+
spinner.fail(chalk19.red(`Failed to fetch secrets: ${e.message}`));
|
|
3267
|
+
}
|
|
3268
|
+
});
|
|
3269
|
+
return envPull;
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
// src/commands/config.ts
|
|
3273
|
+
init_esm_shims();
|
|
3274
|
+
init_config();
|
|
3275
|
+
import { Command as Command14 } from "commander";
|
|
3276
|
+
import chalk20 from "chalk";
|
|
3277
|
+
function createConfigCommand() {
|
|
3278
|
+
const config2 = new Command14("config");
|
|
3279
|
+
config2.description("View or modify Rigstate configuration").argument("[key]", "Configuration key to view/set (api_key, project_id, api_url)").argument("[value]", "Value to set").action(async (key, value) => {
|
|
3280
|
+
if (!key) {
|
|
3281
|
+
console.log(chalk20.bold("Rigstate Configuration"));
|
|
3282
|
+
console.log(chalk20.dim("\u2500".repeat(40)));
|
|
3283
|
+
try {
|
|
3284
|
+
const apiKey = getApiKey();
|
|
3285
|
+
console.log(`${chalk20.cyan("api_key")}: ${apiKey.substring(0, 20)}...`);
|
|
3286
|
+
} catch (e) {
|
|
3287
|
+
console.log(`${chalk20.cyan("api_key")}: ${chalk20.dim("(not set)")}`);
|
|
3288
|
+
}
|
|
3289
|
+
const projectId = getProjectId();
|
|
3290
|
+
console.log(`${chalk20.cyan("project_id")}: ${projectId || chalk20.dim("(not set)")}`);
|
|
3291
|
+
const apiUrl = getApiUrl();
|
|
3292
|
+
console.log(`${chalk20.cyan("api_url")}: ${apiUrl}`);
|
|
3293
|
+
console.log("");
|
|
3294
|
+
console.log(chalk20.dim('Use "rigstate config <key> <value>" to set a value.'));
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
if (!value) {
|
|
3298
|
+
switch (key) {
|
|
3299
|
+
case "api_key":
|
|
3300
|
+
try {
|
|
3301
|
+
const apiKey = getApiKey();
|
|
3302
|
+
console.log(apiKey);
|
|
3303
|
+
} catch (e) {
|
|
3304
|
+
console.log(chalk20.dim("(not set)"));
|
|
3305
|
+
}
|
|
3306
|
+
break;
|
|
3307
|
+
case "project_id":
|
|
3308
|
+
console.log(getProjectId() || chalk20.dim("(not set)"));
|
|
3309
|
+
break;
|
|
3310
|
+
case "api_url":
|
|
3311
|
+
console.log(getApiUrl());
|
|
3312
|
+
break;
|
|
3313
|
+
default:
|
|
3314
|
+
console.log(chalk20.red(`Unknown config key: ${key}`));
|
|
3315
|
+
console.log(chalk20.dim("Valid keys: api_key, project_id, api_url"));
|
|
3316
|
+
}
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
switch (key) {
|
|
3320
|
+
case "api_key":
|
|
3321
|
+
setApiKey(value);
|
|
3322
|
+
console.log(chalk20.green(`\u2705 api_key updated`));
|
|
3323
|
+
break;
|
|
3324
|
+
case "project_id":
|
|
3325
|
+
setProjectId(value);
|
|
3326
|
+
console.log(chalk20.green(`\u2705 project_id updated`));
|
|
3327
|
+
break;
|
|
3328
|
+
case "api_url":
|
|
3329
|
+
console.log(chalk20.yellow("api_url is set via RIGSTATE_API_URL environment variable"));
|
|
3330
|
+
break;
|
|
3331
|
+
default:
|
|
3332
|
+
console.log(chalk20.red(`Unknown config key: ${key}`));
|
|
3333
|
+
console.log(chalk20.dim("Valid keys: api_key, project_id"));
|
|
3334
|
+
}
|
|
3335
|
+
});
|
|
3336
|
+
return config2;
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
// src/commands/mcp.ts
|
|
3340
|
+
init_esm_shims();
|
|
3341
|
+
import { Command as Command15 } from "commander";
|
|
3342
|
+
import chalk21 from "chalk";
|
|
3343
|
+
import { spawn } from "child_process";
|
|
3344
|
+
import path23 from "path";
|
|
3345
|
+
import fs22 from "fs";
|
|
3346
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3347
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
3348
|
+
var __dirname2 = path23.dirname(__filename2);
|
|
3349
|
+
function createMcpCommand() {
|
|
3350
|
+
const mcp = new Command15("mcp");
|
|
3351
|
+
mcp.description("Run the Rigstate MCP server for AI editors").action(async () => {
|
|
3352
|
+
const possiblePaths = [
|
|
3353
|
+
// From packages/cli -> packages/mcp (sibling package)
|
|
3354
|
+
path23.resolve(__dirname2, "../../mcp/dist/index.js"),
|
|
3355
|
+
// If installed globally or via npm
|
|
3356
|
+
path23.resolve(__dirname2, "../../../mcp/dist/index.js"),
|
|
3357
|
+
// Development path from packages/cli/dist
|
|
3358
|
+
path23.resolve(__dirname2, "../../../packages/mcp/dist/index.js")
|
|
3359
|
+
];
|
|
3360
|
+
let serverPath = "";
|
|
3361
|
+
for (const p of possiblePaths) {
|
|
3362
|
+
if (fs22.existsSync(p)) {
|
|
3363
|
+
serverPath = p;
|
|
3364
|
+
break;
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
if (!serverPath) {
|
|
3368
|
+
console.error(chalk21.red("\u274C Error: Rigstate MCP Server binary not found."));
|
|
3369
|
+
console.error(chalk21.yellow("Please ensure that the mcp package is built:"));
|
|
3370
|
+
console.error(chalk21.white(" cd packages/mcp && npm run build"));
|
|
3371
|
+
console.error("");
|
|
3372
|
+
console.error(chalk21.dim("Or run directly with:"));
|
|
3373
|
+
console.error(chalk21.white(" npx @rigstate/mcp"));
|
|
3374
|
+
process.exit(1);
|
|
3375
|
+
}
|
|
3376
|
+
console.log(chalk21.dim(`Starting MCP server from: ${serverPath}`));
|
|
3377
|
+
if (process.env.VIBE_API_KEY && !process.env.RIGSTATE_API_KEY) {
|
|
3378
|
+
process.env.RIGSTATE_API_KEY = process.env.VIBE_API_KEY;
|
|
3379
|
+
}
|
|
3380
|
+
const worker = spawn("node", [serverPath], {
|
|
3381
|
+
env: process.env,
|
|
3382
|
+
stdio: ["inherit", "inherit", "inherit"]
|
|
3383
|
+
});
|
|
3384
|
+
worker.on("error", (err) => {
|
|
3385
|
+
console.error(chalk21.red(`\u274C Failed to start MCP server: ${err.message}`));
|
|
3386
|
+
process.exit(1);
|
|
3387
|
+
});
|
|
3388
|
+
worker.on("exit", (code) => {
|
|
3389
|
+
if (code !== 0 && code !== null) {
|
|
3390
|
+
process.exit(code);
|
|
3391
|
+
}
|
|
3392
|
+
});
|
|
3393
|
+
});
|
|
3394
|
+
return mcp;
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
// src/commands/nexus.ts
|
|
3398
|
+
init_esm_shims();
|
|
3399
|
+
import { Command as Command16 } from "commander";
|
|
3400
|
+
import chalk24 from "chalk";
|
|
3401
|
+
|
|
3402
|
+
// src/nexus/dispatcher.ts
|
|
3403
|
+
init_esm_shims();
|
|
3404
|
+
import EventEmitter4 from "events";
|
|
3405
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3406
|
+
|
|
3407
|
+
// src/hive/gateway.ts
|
|
3408
|
+
init_esm_shims();
|
|
3409
|
+
import axios16 from "axios";
|
|
3410
|
+
|
|
3411
|
+
// src/hive/scrubber.ts
|
|
3412
|
+
init_esm_shims();
|
|
3413
|
+
var HiveScrubber = class {
|
|
3414
|
+
// Patterns that definitely identify a project and MUST be removed
|
|
3415
|
+
static SENSITIVE_PATTERNS = [
|
|
3416
|
+
/(api_key|secret|token|password)[\s]*[:=][\s]*['"][a-zA-Z0-9_\-]+['"]/gi,
|
|
3417
|
+
// Secrets
|
|
3418
|
+
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
3419
|
+
// Emails
|
|
3420
|
+
/(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\. (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/g,
|
|
3421
|
+
// IPs
|
|
3422
|
+
/postgres:\/\/[^:]+:[^@]+@/g
|
|
3423
|
+
// DB Connection Strings
|
|
3424
|
+
];
|
|
3425
|
+
// Generic replacements for project-specific terms to keep the rule abstract
|
|
3426
|
+
static ABSTRACTION_MAP = {
|
|
3427
|
+
"Vibeline": "{PROJECT_NAME}",
|
|
3428
|
+
"Rigstate": "{FRAMEWORK}",
|
|
3429
|
+
"Steinhofve": "{USER}"
|
|
3430
|
+
};
|
|
3431
|
+
/**
|
|
3432
|
+
* Scrubs a string (rule content or log excerpt) of sensitive data.
|
|
3433
|
+
*/
|
|
3434
|
+
static scrub(content, customTerms = []) {
|
|
3435
|
+
let scrubbed = content;
|
|
3436
|
+
let count = 0;
|
|
3437
|
+
let risk = 0;
|
|
3438
|
+
this.SENSITIVE_PATTERNS.forEach((pattern) => {
|
|
3439
|
+
if (pattern.test(scrubbed)) {
|
|
3440
|
+
scrubbed = scrubbed.replace(pattern, "[REDACTED_CREDENTIAL]");
|
|
3441
|
+
count++;
|
|
3442
|
+
risk += 50;
|
|
3443
|
+
}
|
|
3444
|
+
});
|
|
3445
|
+
Object.entries(this.ABSTRACTION_MAP).forEach(([term, replacement]) => {
|
|
3446
|
+
const regex = new RegExp(term, "gi");
|
|
3447
|
+
if (regex.test(scrubbed)) {
|
|
3448
|
+
scrubbed = scrubbed.replace(regex, replacement);
|
|
3449
|
+
count++;
|
|
3450
|
+
}
|
|
3451
|
+
});
|
|
3452
|
+
customTerms.forEach((term) => {
|
|
3453
|
+
const regex = new RegExp(term, "gi");
|
|
3454
|
+
if (regex.test(scrubbed)) {
|
|
3455
|
+
scrubbed = scrubbed.replace(regex, "{ENTITY}");
|
|
3456
|
+
count++;
|
|
3457
|
+
}
|
|
3458
|
+
});
|
|
3459
|
+
return {
|
|
3460
|
+
sanitizedContent: scrubbed,
|
|
3461
|
+
redactionCount: count,
|
|
3462
|
+
riskScore: Math.min(risk, 100)
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3465
|
+
};
|
|
3466
|
+
|
|
3467
|
+
// src/hive/gateway.ts
|
|
3468
|
+
import chalk22 from "chalk";
|
|
3469
|
+
var HiveGateway = class {
|
|
3470
|
+
client;
|
|
3471
|
+
enabled;
|
|
3472
|
+
lastSignalTime = 0;
|
|
3473
|
+
MIN_INTERVAL_MS = 5e3;
|
|
3474
|
+
// Throttle: Max 1 signal per 5s
|
|
3475
|
+
constructor(baseUrl, token) {
|
|
3476
|
+
this.enabled = !!token;
|
|
3477
|
+
if (!this.enabled) {
|
|
3478
|
+
console.log(chalk22.dim("\u26A0\uFE0F Hive Gateway disabled (No Token provided). Running in localized mode."));
|
|
3479
|
+
}
|
|
3480
|
+
this.client = axios16.create({
|
|
3481
|
+
baseURL: baseUrl,
|
|
3482
|
+
headers: {
|
|
3483
|
+
"Authorization": `Bearer ${token}`,
|
|
3484
|
+
"Content-Type": "application/json",
|
|
3485
|
+
"X-Rigstate-Client": "CLI-0.2.0"
|
|
3486
|
+
},
|
|
3487
|
+
timeout: 5e3
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
/**
|
|
3491
|
+
* Transmit an Immune Signal to the Hive.
|
|
3492
|
+
* Includes Pre-Flight Scrubbing and Throttling.
|
|
3493
|
+
*/
|
|
3494
|
+
async transmit(signal) {
|
|
3495
|
+
if (!this.enabled) return false;
|
|
3496
|
+
const now = Date.now();
|
|
3497
|
+
if (now - this.lastSignalTime < this.MIN_INTERVAL_MS) {
|
|
3498
|
+
console.warn(chalk22.yellow("\u23F3 Hive Gateway Throttled. Signal dropped to preventing spam."));
|
|
3499
|
+
return false;
|
|
3500
|
+
}
|
|
3501
|
+
const scrubResult = HiveScrubber.scrub(signal.ruleContent);
|
|
3502
|
+
if (scrubResult.riskScore > 20) {
|
|
3503
|
+
console.error(chalk22.red(`\u{1F6D1} HIVE BLOCKED: Signal contains sensitive data (Risk: ${scrubResult.riskScore})`));
|
|
3504
|
+
return false;
|
|
3505
|
+
}
|
|
3506
|
+
try {
|
|
3507
|
+
console.log(chalk22.blue(`\u{1F4E1} Uplinking to Hive... [${signal.vector}]`));
|
|
3508
|
+
const payload = { ...signal, ruleContent: scrubResult.sanitizedContent };
|
|
3509
|
+
await this.client.post("/signal", payload);
|
|
3510
|
+
this.lastSignalTime = now;
|
|
3511
|
+
console.log(chalk22.green("\u2705 Signal Received by Hive Core. Knowledge Shared."));
|
|
3512
|
+
return true;
|
|
3513
|
+
} catch (error) {
|
|
3514
|
+
console.error(chalk22.red(`\u274C Hive Transmission Failed: ${error.message}`));
|
|
3515
|
+
return false;
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
};
|
|
3519
|
+
|
|
3520
|
+
// src/utils/logger.ts
|
|
3521
|
+
init_esm_shims();
|
|
3522
|
+
import chalk23 from "chalk";
|
|
3523
|
+
var Logger = class {
|
|
3524
|
+
static formatMessage(level, message, context) {
|
|
3525
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3526
|
+
let prefix = "";
|
|
3527
|
+
switch (level) {
|
|
3528
|
+
case "INFO" /* INFO */:
|
|
3529
|
+
prefix = chalk23.blue(`[${"INFO" /* INFO */}]`);
|
|
3530
|
+
break;
|
|
3531
|
+
case "WARN" /* WARN */:
|
|
3532
|
+
prefix = chalk23.yellow(`[${"WARN" /* WARN */}]`);
|
|
3533
|
+
break;
|
|
3534
|
+
case "ERROR" /* ERROR */:
|
|
3535
|
+
prefix = chalk23.red(`[${"ERROR" /* ERROR */}]`);
|
|
3536
|
+
break;
|
|
3537
|
+
case "DEBUG" /* DEBUG */:
|
|
3538
|
+
prefix = chalk23.gray(`[${"DEBUG" /* DEBUG */}]`);
|
|
3539
|
+
break;
|
|
3540
|
+
}
|
|
3541
|
+
let output = `${chalk23.gray(timestamp)} ${prefix} ${message}`;
|
|
3542
|
+
if (context) {
|
|
3543
|
+
if (context instanceof Error) {
|
|
3544
|
+
output += `
|
|
3545
|
+
${chalk23.red(context.stack || context.message)}`;
|
|
3546
|
+
} else if (typeof context === "object") {
|
|
3547
|
+
try {
|
|
3548
|
+
output += `
|
|
3549
|
+
${chalk23.gray(JSON.stringify(context, null, 2))}`;
|
|
3550
|
+
} catch (e) {
|
|
3551
|
+
output += `
|
|
3552
|
+
${chalk23.gray("[Circular or invalid object]")}`;
|
|
3553
|
+
}
|
|
3554
|
+
} else {
|
|
3555
|
+
output += ` ${String(context)}`;
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
return output;
|
|
3559
|
+
}
|
|
3560
|
+
static info(message, context) {
|
|
3561
|
+
console.log(this.formatMessage("INFO" /* INFO */, message, context));
|
|
3562
|
+
}
|
|
3563
|
+
static warn(message, context) {
|
|
3564
|
+
console.warn(this.formatMessage("WARN" /* WARN */, message, context));
|
|
3565
|
+
}
|
|
3566
|
+
static error(message, error) {
|
|
3567
|
+
console.error(this.formatMessage("ERROR" /* ERROR */, message, error));
|
|
3568
|
+
}
|
|
3569
|
+
static debug(message, context) {
|
|
3570
|
+
if (process.env.DEBUG || process.env.RIGSTATE_DEBUG) {
|
|
3571
|
+
console.debug(this.formatMessage("DEBUG" /* DEBUG */, message, context));
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
};
|
|
3575
|
+
|
|
3576
|
+
// src/nexus/dispatcher.ts
|
|
3577
|
+
var NexusDispatcher = class extends EventEmitter4 {
|
|
3578
|
+
context;
|
|
3579
|
+
orderQueue = [];
|
|
3580
|
+
orderHistory = [];
|
|
3581
|
+
gateway;
|
|
3582
|
+
constructor(context) {
|
|
3583
|
+
super();
|
|
3584
|
+
this.context = context;
|
|
3585
|
+
this.gateway = new HiveGateway(
|
|
3586
|
+
process.env.RIGSTATE_HIVE_URL || "https://rigstate.com/api/hive",
|
|
3587
|
+
process.env.RIGSTATE_HIVE_TOKEN
|
|
3588
|
+
);
|
|
3589
|
+
Logger.info(`\u{1F9E0} NEXUS DISPATCHER ONLINE. Context: ${context.projectId} (DryRun: ${context.dryRun})`);
|
|
3590
|
+
}
|
|
3591
|
+
/**
|
|
3592
|
+
* Creates a new Service Order and routes it.
|
|
3593
|
+
*/
|
|
3594
|
+
async dispatch(source, target, intent, action, params, constraints = []) {
|
|
3595
|
+
const order = {
|
|
3596
|
+
id: uuidv4(),
|
|
3597
|
+
traceId: uuidv4(),
|
|
3598
|
+
// TODO: Inherit traceId if chained
|
|
3599
|
+
sourceAgent: source,
|
|
3600
|
+
targetAgent: target,
|
|
3601
|
+
priority: "NORMAL",
|
|
3602
|
+
intent,
|
|
3603
|
+
action,
|
|
3604
|
+
parameters: params,
|
|
3605
|
+
constraints,
|
|
3606
|
+
status: "PENDING",
|
|
3607
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3608
|
+
};
|
|
3609
|
+
this.orderQueue.push(order);
|
|
3610
|
+
this.emit("order:created", order);
|
|
3611
|
+
if (target === "EITRI" && order.action.startsWith("fs.write")) {
|
|
3612
|
+
if (this.context.dryRun) {
|
|
3613
|
+
Logger.info(`\u{1F6D1} NEXUS KILL-SWITCH: Order ${order.id} blocked by Dry-Run protocol.`);
|
|
3614
|
+
order.status = "PENDING";
|
|
3615
|
+
this.emit("order:blocked", order);
|
|
3616
|
+
return order;
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
return this.executeOrder(order);
|
|
3620
|
+
}
|
|
3621
|
+
/**
|
|
3622
|
+
* Executes the order (simulated for now, essentially "Sending" it)
|
|
3623
|
+
*/
|
|
3624
|
+
async executeOrder(order) {
|
|
3625
|
+
order.status = "EXECUTING";
|
|
3626
|
+
order.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3627
|
+
this.emit("order:started", order);
|
|
3628
|
+
try {
|
|
3629
|
+
Logger.info(`\u{1F680} NEXUS: Routing Order ${order.id} [${order.sourceAgent} -> ${order.targetAgent}]: ${order.intent}`);
|
|
3630
|
+
if (order.targetAgent === "MAJA" && order.action === "HIVE_TRANSMIT") {
|
|
3631
|
+
const signal = order.parameters.signal;
|
|
3632
|
+
if (signal) {
|
|
3633
|
+
await this.gateway.transmit(signal);
|
|
3634
|
+
order.status = "COMPLETED";
|
|
3635
|
+
return order;
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
this.emit(`agent:${order.targetAgent}`, order);
|
|
3639
|
+
return order;
|
|
3640
|
+
} catch (error) {
|
|
3641
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
3642
|
+
Logger.error(`Dispatch failed for order ${order.id}`, error);
|
|
3643
|
+
order.status = "FAILED";
|
|
3644
|
+
order.error = {
|
|
3645
|
+
code: "DISPATCH_ERROR",
|
|
3646
|
+
message: errorMessage
|
|
3647
|
+
};
|
|
3648
|
+
this.emit("order:failed", order);
|
|
3649
|
+
return order;
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
/**
|
|
3653
|
+
* Human Approval (The "Red Button")
|
|
3654
|
+
*/
|
|
3655
|
+
async approveOrder(orderId) {
|
|
3656
|
+
const order = this.orderQueue.find((o) => o.id === orderId);
|
|
3657
|
+
if (!order) throw new Error(`Order ${orderId} not found`);
|
|
3658
|
+
if (order.status !== "AWAITING_APPROVAL") {
|
|
3659
|
+
Logger.warn(`Order ${orderId} is not awaiting approval (Status: ${order.status})`);
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
Logger.info(`\u2705 HUMAN APPROVED Order ${orderId}`);
|
|
3663
|
+
await this.executeOrder(order);
|
|
3664
|
+
}
|
|
3665
|
+
};
|
|
3666
|
+
|
|
3667
|
+
// src/commands/nexus.ts
|
|
3668
|
+
import inquirer3 from "inquirer";
|
|
3669
|
+
function createNexusCommand() {
|
|
3670
|
+
const command = new Command16("nexus");
|
|
3671
|
+
command.description("Interact with The Multi-Agent Nexus (Phase 8)").argument("<intent>", "The natural language instruction for the swarm").option("--dry-run", "Enable Dry-Run mode (Kill-Switch Active)", true).option("--force", "Disable Dry-Run mode (DANGEROUS)", false).action(async (intent, options) => {
|
|
3672
|
+
console.log(chalk24.bold.magenta("\n\u{1F981} Welcome to The Nexus (Phase 8)\n"));
|
|
3673
|
+
const dryRun = !options.force;
|
|
3674
|
+
if (!dryRun) {
|
|
3675
|
+
console.log(chalk24.black.bgYellow(" WARNING ") + chalk24.yellow(" Dry-Run disabled! Eitri is authorized to write code."));
|
|
3676
|
+
const { confirm } = await inquirer3.prompt([{
|
|
3677
|
+
type: "confirm",
|
|
3678
|
+
name: "confirm",
|
|
3679
|
+
message: "Are you absolutely sure you want to bypass the Kill-Switch?",
|
|
3680
|
+
default: false
|
|
3681
|
+
}]);
|
|
3682
|
+
if (!confirm) {
|
|
3683
|
+
console.log("Aborting.");
|
|
3684
|
+
process.exit(0);
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
const context = {
|
|
3688
|
+
projectId: process.env.RIGSTATE_PROJECT_ID || "local",
|
|
3689
|
+
rootPath: process.cwd(),
|
|
3690
|
+
sessionUser: "cli-user",
|
|
3691
|
+
// Should strictly be pulled from auth
|
|
3692
|
+
dryRun
|
|
3693
|
+
};
|
|
3694
|
+
const dispatcher = new NexusDispatcher(context);
|
|
3695
|
+
dispatcher.on("order:created", (o) => {
|
|
3696
|
+
console.log(chalk24.blue(`\u{1F195} [${o.id.slice(0, 6)}] Order Created: `) + o.intent);
|
|
3697
|
+
});
|
|
3698
|
+
dispatcher.on("order:started", (o) => {
|
|
3699
|
+
console.log(chalk24.yellow(`\u23F3 [${o.id.slice(0, 6)}] Processing...`));
|
|
3700
|
+
});
|
|
3701
|
+
dispatcher.on("order:blocked", (o) => {
|
|
3702
|
+
console.log(chalk24.red(`\u{1F6D1} [${o.id.slice(0, 6)}] BLOCKED by Kill-Switch`));
|
|
3703
|
+
console.log(chalk24.dim(` Target: ${o.targetAgent} | Action: ${o.action}`));
|
|
3704
|
+
console.log(chalk24.dim(" Run with --force to execute automatically (NOT RECOMMENDED)."));
|
|
3705
|
+
});
|
|
3706
|
+
dispatcher.on("agent:SINDRE", (o) => console.log(chalk24.cyan(`\u{1F916} Sindre (Vault): I'm on it! (${o.action})`)));
|
|
3707
|
+
dispatcher.on("agent:EITRI", (o) => console.log(chalk24.green(`\u{1F477} Eitri (Smith): Ready to build! (${o.action})`)));
|
|
3708
|
+
console.log(chalk24.dim("\u{1F9E0} Frank is analyzing your intent..."));
|
|
3709
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
3710
|
+
if (intent.toLowerCase().includes("db") || intent.toLowerCase().includes("database")) {
|
|
3711
|
+
await dispatcher.dispatch("FRANK", "SINDRE", intent, "db.analyze", { raw: intent });
|
|
3712
|
+
} else if (intent.toLowerCase().includes("create") || intent.toLowerCase().includes("code")) {
|
|
3713
|
+
await dispatcher.dispatch("FRANK", "EITRI", intent, "fs.write", { path: "src/demo.ts", content: "// demo" });
|
|
3714
|
+
} else {
|
|
3715
|
+
console.log(chalk24.gray("Frank didn't understand. Try 'create file' or 'check database'."));
|
|
3716
|
+
}
|
|
3717
|
+
});
|
|
3718
|
+
return command;
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3721
|
+
// src/commands/sync-rules.ts
|
|
3722
|
+
init_esm_shims();
|
|
3723
|
+
init_config();
|
|
3724
|
+
import { Command as Command17 } from "commander";
|
|
3725
|
+
import chalk25 from "chalk";
|
|
3726
|
+
import ora11 from "ora";
|
|
3727
|
+
import axios17 from "axios";
|
|
3728
|
+
function createSyncRulesCommand() {
|
|
3729
|
+
const syncRules = new Command17("sync-rules");
|
|
3730
|
+
syncRules.description("\u{1F6E1}\uFE0F Push Frank Protocol v1.0 to all existing projects").option("--dry-run", "Preview changes without pushing to GitHub").option("--project <id>", "Sync a specific project only").action(async (options) => {
|
|
3731
|
+
const spinner = ora11("\u{1F6E1}\uFE0F Frank Protocol: Initializing retroactive sync...").start();
|
|
3732
|
+
const results = [];
|
|
3733
|
+
let apiKey;
|
|
3734
|
+
try {
|
|
3735
|
+
apiKey = getApiKey();
|
|
3736
|
+
} catch (e) {
|
|
3737
|
+
spinner.fail(chalk25.red('Not authenticated. Run "rigstate login" first.'));
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
const apiUrl = getApiUrl();
|
|
3741
|
+
try {
|
|
3742
|
+
spinner.text = "Fetching projects...";
|
|
3743
|
+
const projectsResponse = await axios17.get(`${apiUrl}/api/v1/projects`, {
|
|
3744
|
+
params: options.project ? { project_id: options.project } : {},
|
|
3745
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
3746
|
+
});
|
|
3747
|
+
if (!projectsResponse.data.success) {
|
|
3748
|
+
throw new Error(projectsResponse.data.error || "Failed to fetch projects");
|
|
3749
|
+
}
|
|
3750
|
+
let projects = projectsResponse.data.data.projects || [];
|
|
3751
|
+
if (projects.length === 0) {
|
|
3752
|
+
spinner.fail(chalk25.red("No projects found."));
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
if (projects.length > 1 && !options.project) {
|
|
3756
|
+
spinner.stop();
|
|
3757
|
+
const inquirer4 = (await import("inquirer")).default;
|
|
3758
|
+
const { selectedProjectId } = await inquirer4.prompt([{
|
|
3759
|
+
type: "list",
|
|
3760
|
+
name: "selectedProjectId",
|
|
3761
|
+
message: "Multiple projects found. Which one do you want to sync?",
|
|
3762
|
+
choices: projects.map((p) => ({
|
|
3763
|
+
name: `${p.name} [${p.id}]`,
|
|
3764
|
+
value: p.id
|
|
3765
|
+
}))
|
|
3766
|
+
}]);
|
|
3767
|
+
projects = projects.filter((p) => p.id === selectedProjectId);
|
|
3768
|
+
options.project = selectedProjectId;
|
|
3769
|
+
try {
|
|
3770
|
+
const fs23 = await import("fs/promises");
|
|
3771
|
+
const path24 = await import("path");
|
|
3772
|
+
const envPath = path24.join(process.cwd(), ".env");
|
|
3773
|
+
const envLocalPath = path24.join(process.cwd(), ".env.local");
|
|
3774
|
+
let targetEnv = envLocalPath;
|
|
3775
|
+
try {
|
|
3776
|
+
await fs23.access(envLocalPath);
|
|
3777
|
+
} catch {
|
|
3778
|
+
try {
|
|
3779
|
+
await fs23.access(envPath);
|
|
3780
|
+
targetEnv = envPath;
|
|
3781
|
+
} catch {
|
|
3782
|
+
targetEnv = envPath;
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
let content = "";
|
|
3786
|
+
try {
|
|
3787
|
+
content = await fs23.readFile(targetEnv, "utf-8");
|
|
3788
|
+
} catch {
|
|
3789
|
+
}
|
|
3790
|
+
if (!content.includes("RIGSTATE_PROJECT_ID")) {
|
|
3791
|
+
const newContent = content.endsWith("\n") || content === "" ? `${content}RIGSTATE_PROJECT_ID=${selectedProjectId}
|
|
3792
|
+
` : `${content}
|
|
3793
|
+
RIGSTATE_PROJECT_ID=${selectedProjectId}
|
|
3794
|
+
`;
|
|
3795
|
+
await fs23.writeFile(targetEnv, newContent, "utf-8");
|
|
3796
|
+
console.log(chalk25.dim(` \u{1F4BE} Saved default project to ${path24.basename(targetEnv)}`));
|
|
3797
|
+
}
|
|
3798
|
+
} catch (e) {
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
spinner.succeed(`Syncing project: ${projects[0].name}`);
|
|
3802
|
+
for (const project of projects) {
|
|
3803
|
+
const projectSpinner = ora11(` Syncing: ${project.name}...`).start();
|
|
3804
|
+
try {
|
|
3805
|
+
if (options.dryRun) {
|
|
3806
|
+
projectSpinner.succeed(chalk25.yellow(` [DRY-RUN] Would sync: ${project.name}`));
|
|
3807
|
+
results.push({ projectId: project.id, projectName: project.name, status: "success" });
|
|
3808
|
+
continue;
|
|
3809
|
+
}
|
|
3810
|
+
const syncResponse = await axios17.post(`${apiUrl}/api/v1/rules/sync`, {
|
|
3811
|
+
project_id: project.id
|
|
3812
|
+
}, {
|
|
3813
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
3814
|
+
});
|
|
3815
|
+
if (syncResponse.data.success) {
|
|
3816
|
+
if (syncResponse.data.data.github_synced) {
|
|
3817
|
+
projectSpinner.succeed(chalk25.green(` \u2705 ${project.name} [${project.id}] \u2192 GitHub synced`));
|
|
3818
|
+
} else {
|
|
3819
|
+
projectSpinner.info(chalk25.blue(` \u2139\uFE0F ${project.name} [${project.id}] \u2192 Rules generated (no GitHub)`));
|
|
3820
|
+
}
|
|
3821
|
+
const files = syncResponse.data.data.files;
|
|
3822
|
+
if (files && Array.isArray(files) && (projects.length === 1 || options.project)) {
|
|
3823
|
+
const fs23 = await import("fs/promises");
|
|
3824
|
+
const path24 = await import("path");
|
|
3825
|
+
for (const file of files) {
|
|
3826
|
+
const filePath = path24.join(process.cwd(), file.path);
|
|
3827
|
+
await fs23.mkdir(path24.dirname(filePath), { recursive: true });
|
|
3828
|
+
await fs23.writeFile(filePath, file.content, "utf-8");
|
|
3829
|
+
}
|
|
3830
|
+
console.log(chalk25.dim(` \u{1F4BE} Wrote ${files.length} rule files to local .cursor/rules/`));
|
|
3831
|
+
}
|
|
3832
|
+
results.push({ projectId: project.id, projectName: project.name, status: "success" });
|
|
3833
|
+
} else {
|
|
3834
|
+
projectSpinner.warn(chalk25.yellow(` \u26A0\uFE0F ${project.name} \u2192 ${syncResponse.data.error || "Unknown error"}`));
|
|
3835
|
+
results.push({ projectId: project.id, projectName: project.name, status: "failed", error: syncResponse.data.error });
|
|
3836
|
+
}
|
|
3837
|
+
} catch (e) {
|
|
3838
|
+
projectSpinner.fail(chalk25.red(` \u274C ${project.name}: ${e.message}`));
|
|
3839
|
+
results.push({ projectId: project.id, projectName: project.name, status: "failed", error: e.message });
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
console.log("");
|
|
3843
|
+
console.log(chalk25.bold("\u{1F4CA} Sync Summary:"));
|
|
3844
|
+
const successful = results.filter((r) => r.status === "success").length;
|
|
3845
|
+
const failed = results.filter((r) => r.status === "failed").length;
|
|
3846
|
+
console.log(chalk25.green(` \u2705 Successful: ${successful}`));
|
|
3847
|
+
if (failed > 0) {
|
|
3848
|
+
console.log(chalk25.red(` \u274C Failed: ${failed}`));
|
|
3849
|
+
}
|
|
3850
|
+
console.log("");
|
|
3851
|
+
console.log(chalk25.cyan("\u{1F6E1}\uFE0F Frank Protocol v1.0 has been injected into the rules engine."));
|
|
3852
|
+
console.log(chalk25.dim(" All new chats will now boot with mandatory governance checks."));
|
|
3853
|
+
} catch (e) {
|
|
3854
|
+
spinner.fail(chalk25.red("Sync failed: " + e.message));
|
|
3855
|
+
}
|
|
3856
|
+
});
|
|
3857
|
+
return syncRules;
|
|
3858
|
+
}
|
|
3859
|
+
|
|
3860
|
+
// src/commands/override.ts
|
|
3861
|
+
init_esm_shims();
|
|
3862
|
+
init_governance();
|
|
3863
|
+
init_config();
|
|
3864
|
+
import { Command as Command18 } from "commander";
|
|
3865
|
+
import chalk26 from "chalk";
|
|
3866
|
+
import axios18 from "axios";
|
|
3867
|
+
function createOverrideCommand() {
|
|
3868
|
+
const override = new Command18("override");
|
|
3869
|
+
override.description("Emergency Override for Governance Soft Locks").argument("<violationId>", 'ID of the violation to override (or "all")').requiredOption("-r, --reason <reason>", "Description of why this override is necessary").action(async (violationId, options) => {
|
|
3870
|
+
const { reason } = options;
|
|
3871
|
+
console.log(chalk26.bold(`
|
|
3872
|
+
\u{1F513} Initiating Governance Override Protocol...`));
|
|
3873
|
+
const session = await getSessionState(process.cwd());
|
|
3874
|
+
if (session.status !== "SOFT_LOCK") {
|
|
3875
|
+
console.log(chalk26.yellow(" Info: Session is not currently locked."));
|
|
3876
|
+
return;
|
|
3877
|
+
}
|
|
3878
|
+
console.log(chalk26.dim(` Active Violation: ${session.active_violation}`));
|
|
3879
|
+
console.log(chalk26.dim(` Reason Provided: "${reason}"`));
|
|
3880
|
+
const success = await performOverride(violationId, reason, process.cwd());
|
|
3881
|
+
if (success) {
|
|
3882
|
+
console.log(chalk26.green(` \u2705 Session UNLOCKED.`));
|
|
3883
|
+
console.log(chalk26.dim(` This event has been logged to the Mission Report.`));
|
|
3884
|
+
try {
|
|
3885
|
+
const projectId = getProjectId();
|
|
3886
|
+
if (projectId) {
|
|
3887
|
+
const apiUrl = getApiUrl();
|
|
3888
|
+
const apiKey = getApiKey();
|
|
3889
|
+
await axios18.post(`${apiUrl}/api/v1/execution-logs`, {
|
|
3890
|
+
project_id: projectId,
|
|
3891
|
+
task_id: "OVERRIDE-" + Date.now(),
|
|
3892
|
+
task_title: `Governance Override: ${violationId}`,
|
|
3893
|
+
status: "COMPLETED",
|
|
3894
|
+
execution_summary: `Manual override executed. Reason: ${reason}`,
|
|
3895
|
+
agent_role: "SUPERVISOR"
|
|
3896
|
+
// Override is a supervisor action
|
|
3897
|
+
}, {
|
|
3898
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
3899
|
+
});
|
|
3900
|
+
console.log(chalk26.dim(` \u2601 Audit log synced to Cloud.`));
|
|
3901
|
+
}
|
|
3902
|
+
} catch (e) {
|
|
3903
|
+
console.log(chalk26.dim(` (Cloud audit sync failed: ${e.message})`));
|
|
3904
|
+
}
|
|
3905
|
+
} else {
|
|
3906
|
+
console.log(chalk26.red(` \u{1F6D1} Override Failed. Check project configuration.`));
|
|
3907
|
+
}
|
|
3908
|
+
});
|
|
3909
|
+
return override;
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
// src/utils/version.ts
|
|
3913
|
+
init_esm_shims();
|
|
3914
|
+
async function checkVersion() {
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
// src/index.ts
|
|
3918
|
+
import dotenv from "dotenv";
|
|
3919
|
+
dotenv.config();
|
|
3920
|
+
var program = new Command19();
|
|
3921
|
+
program.name("rigstate").description("CLI for Rigstate - The AI-Native Dev Studio").version("0.2.0");
|
|
3922
|
+
program.addCommand(createLoginCommand());
|
|
3923
|
+
program.addCommand(createLinkCommand());
|
|
3924
|
+
program.addCommand(createScanCommand());
|
|
3925
|
+
program.addCommand(createFixCommand());
|
|
3926
|
+
program.addCommand(createSyncCommand());
|
|
3927
|
+
program.addCommand(createInitCommand());
|
|
3928
|
+
program.addCommand(createCheckCommand());
|
|
3929
|
+
program.addCommand(createHooksCommand());
|
|
3930
|
+
program.addCommand(createDaemonCommand());
|
|
3931
|
+
program.addCommand(createWorkCommand());
|
|
3932
|
+
program.addCommand(createWatchCommand());
|
|
3933
|
+
program.addCommand(createFocusCommand());
|
|
3934
|
+
program.addCommand(createEnvPullCommand());
|
|
3935
|
+
program.addCommand(createConfigCommand());
|
|
3936
|
+
program.addCommand(createMcpCommand());
|
|
3937
|
+
program.addCommand(createNexusCommand());
|
|
3938
|
+
program.addCommand(createSyncRulesCommand());
|
|
3939
|
+
program.addCommand(createOverrideCommand());
|
|
3940
|
+
program.hook("preAction", async () => {
|
|
3941
|
+
await checkVersion();
|
|
3942
|
+
});
|
|
3943
|
+
program.on("--help", () => {
|
|
3944
|
+
console.log("");
|
|
3945
|
+
console.log(chalk27.bold("Examples:"));
|
|
3946
|
+
console.log("");
|
|
3947
|
+
console.log(chalk27.cyan(" $ rigstate login sk_rigstate_your_api_key"));
|
|
3948
|
+
console.log(chalk27.dim(" Authenticate with your Rigstate API key"));
|
|
3949
|
+
console.log("");
|
|
3950
|
+
console.log(chalk27.cyan(" $ rigstate scan"));
|
|
3951
|
+
console.log(chalk27.dim(" Scan the current directory"));
|
|
3952
|
+
console.log("");
|
|
3953
|
+
console.log(chalk27.cyan(" $ rigstate scan ./src --project abc123"));
|
|
3954
|
+
console.log(chalk27.dim(" Scan a specific directory with project ID"));
|
|
3955
|
+
console.log("");
|
|
3956
|
+
console.log(chalk27.cyan(" $ rigstate scan --json"));
|
|
3957
|
+
console.log(chalk27.dim(" Output results in JSON format (useful for IDE extensions)"));
|
|
3958
|
+
console.log("");
|
|
3959
|
+
});
|
|
3960
|
+
program.parse(process.argv);
|
|
3961
|
+
if (!process.argv.slice(2).length) {
|
|
3962
|
+
program.outputHelp();
|
|
3963
|
+
}
|
|
3964
|
+
//# sourceMappingURL=index.js.map
|