runshift 0.0.2 → 0.0.3

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.
Files changed (44) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/dist/commands/init.d.ts +2 -0
  3. package/dist/commands/init.d.ts.map +1 -0
  4. package/dist/commands/init.js +129 -0
  5. package/dist/commands/init.js.map +1 -0
  6. package/dist/context/collector.d.ts +4 -0
  7. package/dist/context/collector.d.ts.map +1 -0
  8. package/dist/context/collector.js +191 -0
  9. package/dist/context/collector.js.map +1 -0
  10. package/dist/context/git.d.ts +7 -0
  11. package/dist/context/git.d.ts.map +1 -0
  12. package/dist/context/git.js +30 -0
  13. package/dist/context/git.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +33 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/types.d.ts +42 -0
  19. package/dist/types.d.ts.map +1 -0
  20. package/dist/types.js +3 -0
  21. package/dist/types.js.map +1 -0
  22. package/dist/ui/display.d.ts +14 -0
  23. package/dist/ui/display.d.ts.map +1 -0
  24. package/dist/ui/display.js +148 -0
  25. package/dist/ui/display.js.map +1 -0
  26. package/dist/ui/prompt.d.ts +4 -0
  27. package/dist/ui/prompt.d.ts.map +1 -0
  28. package/dist/ui/prompt.js +31 -0
  29. package/dist/ui/prompt.js.map +1 -0
  30. package/dist/writer.d.ts +4 -0
  31. package/dist/writer.d.ts.map +1 -0
  32. package/dist/writer.js +29 -0
  33. package/dist/writer.js.map +1 -0
  34. package/package.json +22 -4
  35. package/src/commands/init.ts +163 -0
  36. package/src/context/collector.ts +205 -0
  37. package/src/context/git.ts +36 -0
  38. package/src/index.ts +35 -0
  39. package/src/types.ts +44 -0
  40. package/src/ui/display.ts +164 -0
  41. package/src/ui/prompt.ts +33 -0
  42. package/src/writer.ts +37 -0
  43. package/tsconfig.json +19 -0
  44. package/index.js +0 -2
@@ -0,0 +1,36 @@
1
+ import { execSync } from "node:child_process";
2
+
3
+ export interface GitState {
4
+ isGitRepo: boolean;
5
+ branch: string;
6
+ isDirty: boolean;
7
+ }
8
+
9
+ export function getGitState(): GitState {
10
+ try {
11
+ execSync("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
12
+ } catch {
13
+ return { isGitRepo: false, branch: "", isDirty: false };
14
+ }
15
+
16
+ let branch = "";
17
+ try {
18
+ branch = execSync("git branch --show-current", { stdio: "pipe" })
19
+ .toString()
20
+ .trim();
21
+ } catch {
22
+ branch = "unknown";
23
+ }
24
+
25
+ let isDirty = false;
26
+ try {
27
+ const status = execSync("git status --porcelain", { stdio: "pipe" })
28
+ .toString()
29
+ .trim();
30
+ isDirty = status.length > 0;
31
+ } catch {
32
+ isDirty = false;
33
+ }
34
+
35
+ return { isGitRepo: true, branch, isDirty };
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { init } from "./commands/init.js";
4
+
5
+ const args = process.argv.slice(2);
6
+ const command = args[0] ?? "init";
7
+
8
+ switch (command) {
9
+ case "init":
10
+ init().catch((err) => {
11
+ console.error(err.message ?? err);
12
+ process.exit(1);
13
+ });
14
+ break;
15
+ case "--version":
16
+ case "-v":
17
+ console.log("runshift 0.0.2");
18
+ break;
19
+ case "--help":
20
+ case "-h":
21
+ console.log(`
22
+ runshift — the control plane for agents, wherever they run.
23
+
24
+ Usage:
25
+ npx runshift init Read your repo, generate governance rules
26
+
27
+ Options:
28
+ --version, -v Show version
29
+ --help, -h Show this help
30
+ `);
31
+ break;
32
+ default:
33
+ console.error(`Unknown command: ${command}\nRun "runshift --help" for usage.`);
34
+ process.exit(1);
35
+ }
package/src/types.ts ADDED
@@ -0,0 +1,44 @@
1
+ // ── Shared types mirroring the API contract ──
2
+
3
+ export interface RepoContext {
4
+ packageJson: {
5
+ name?: string;
6
+ description?: string;
7
+ dependencies?: Record<string, string>;
8
+ devDependencies?: Record<string, string>;
9
+ scripts?: Record<string, string>;
10
+ workspaces?: string[] | { packages: string[] };
11
+ };
12
+ tsconfig: Record<string, unknown> | null;
13
+ directoryTree: string[];
14
+ envKeys: string[];
15
+ configFiles: Record<string, string>;
16
+ existingRules: Record<string, string>;
17
+ migrationCount: number;
18
+ migrationNames: string[];
19
+ rootConfigs: string[];
20
+ gitState: {
21
+ branch: string;
22
+ isDirty: boolean;
23
+ } | null;
24
+ }
25
+
26
+ export interface GeneratedFile {
27
+ path: string;
28
+ content: string;
29
+ action: "create" | "update";
30
+ }
31
+
32
+ export interface Findings {
33
+ blastRadius: string[];
34
+ securityGaps: string[];
35
+ agentFailurePatterns: string[];
36
+ parallelizationBoundaries: string[];
37
+ deprecatedPatterns: string[];
38
+ }
39
+
40
+ export interface InitResponse {
41
+ files: GeneratedFile[];
42
+ findings: Findings;
43
+ summary: string;
44
+ }
@@ -0,0 +1,164 @@
1
+ import chalk from "chalk";
2
+ import figlet from "figlet";
3
+ import type { Findings, GeneratedFile, RepoContext } from "../types.js";
4
+
5
+ const amber = chalk.hex("#f5a623");
6
+ const muted = chalk.hex("#6b6b7b");
7
+ const dim = chalk.dim;
8
+ const divider = muted(" " + "─".repeat(45));
9
+
10
+ export function showBanner(): void {
11
+ const banner = figlet.textSync("runshift", {
12
+ font: "Standard",
13
+ horizontalLayout: "default",
14
+ });
15
+ console.log(amber(banner));
16
+ console.log(muted(" v0.0.2"));
17
+ console.log(muted(" the control plane for agents, wherever they run.\n"));
18
+ console.log(divider + "\n");
19
+ }
20
+
21
+ export function showNotGitRepo(): void {
22
+ console.log(amber(" this directory is not a git repository."));
23
+ console.log(dim(" run runshift init from a project root with git initialized.\n"));
24
+ }
25
+
26
+ export function showDirtyWarning(): void {
27
+ console.log(muted(" ⚠ uncommitted changes detected\n"));
28
+ }
29
+
30
+ export function showBranchInfo(branch: string): void {
31
+ console.log(dim(` on branch ${amber(branch)}\n`));
32
+ }
33
+
34
+ export function showScanResults(context: RepoContext): void {
35
+ console.log(amber(" relay scanned your repository:\n"));
36
+
37
+ const deps: Record<string, string> = {
38
+ ...context.packageJson.dependencies,
39
+ ...context.packageJson.devDependencies,
40
+ };
41
+
42
+ const detections: string[] = [];
43
+
44
+ if (context.packageJson.name) {
45
+ const stack: string[] = [];
46
+ if (deps["next"]) stack.push("Next.js");
47
+ if (deps["@supabase/supabase-js"] || deps["@supabase/ssr"]) stack.push("Supabase");
48
+ if (deps["tailwindcss"]) stack.push("Tailwind");
49
+ if (deps["prisma"] || deps["@prisma/client"]) stack.push("Prisma");
50
+ if (deps["drizzle-orm"]) stack.push("Drizzle");
51
+ if (deps["stripe"]) stack.push("Stripe");
52
+ const label = stack.length > 0 ? stack.join(", ") + " detected" : "detected";
53
+ detections.push(`package.json — ${label}`);
54
+ }
55
+
56
+ if (context.envKeys.length > 0) {
57
+ detections.push(`.env.example — ${context.envKeys.length} environment variable${context.envKeys.length === 1 ? "" : "s"} found`);
58
+ }
59
+
60
+ if (context.migrationCount > 0) {
61
+ detections.push(`supabase/migrations/ — ${context.migrationCount} migration file${context.migrationCount === 1 ? "" : "s"} found`);
62
+ }
63
+
64
+ const existingRuleKeys = Object.keys(context.existingRules);
65
+ const cursorRules = existingRuleKeys.filter((k) => k.startsWith(".cursor/rules/"));
66
+ if (cursorRules.length > 0) {
67
+ detections.push(`.cursor/rules/ — ${cursorRules.length} existing file${cursorRules.length === 1 ? "" : "s"} detected`);
68
+ }
69
+
70
+ if (existingRuleKeys.includes("CLAUDE.md")) {
71
+ detections.push("existing CLAUDE.md detected");
72
+ }
73
+
74
+ if (context.tsconfig) {
75
+ detections.push("tsconfig.json detected");
76
+ }
77
+
78
+ const configCount = Object.keys(context.configFiles).length;
79
+ if (configCount > 0) {
80
+ detections.push(`${configCount} config file${configCount === 1 ? "" : "s"} found`);
81
+ }
82
+
83
+ for (const d of detections) {
84
+ console.log(dim(" ✓ ") + d);
85
+ }
86
+ console.log();
87
+ }
88
+
89
+ export function showFindings(findings: Findings): void {
90
+ const sections: [string, string[]][] = [
91
+ ["blast radius", findings.blastRadius],
92
+ ["security gaps", findings.securityGaps],
93
+ ["agent failure patterns", findings.agentFailurePatterns],
94
+ ["parallelization boundaries", findings.parallelizationBoundaries],
95
+ ["deprecated patterns", findings.deprecatedPatterns],
96
+ ];
97
+
98
+ const hasFindings = sections.some(([, items]) => items.length > 0);
99
+ if (!hasFindings) return;
100
+
101
+ console.log(amber(" relay found issues in your codebase:\n"));
102
+
103
+ for (const [title, items] of sections) {
104
+ if (items.length === 0) continue;
105
+ console.log(amber(` ${title}`));
106
+ for (const item of items) {
107
+ console.log(dim(` → ${item}`));
108
+ }
109
+ console.log();
110
+ }
111
+ }
112
+
113
+ export function showFileList(files: GeneratedFile[]): void {
114
+ console.log(amber(` relay will write ${files.length} file${files.length === 1 ? "" : "s"}:\n`));
115
+ for (const file of files) {
116
+ const prefix = file.action === "create" ? amber("+") : amber("~");
117
+ const action = file.action === "create" ? dim("(create)") : dim("(update — existing file)");
118
+ console.log(` ${prefix} ${file.path} ${action}`);
119
+ }
120
+ console.log(dim("\n + = create, ~ = update existing file\n"));
121
+ }
122
+
123
+ export function showWriting(filePath: string): void {
124
+ console.log(dim(" ✓ ") + filePath);
125
+ }
126
+
127
+ export function showCommit(): void {
128
+ console.log(dim(" ✓ ") + "committed");
129
+ }
130
+
131
+ export function showSummary(summary: string): void {
132
+ console.log(muted(` ${summary.replace(/\n/g, "\n ")}\n`));
133
+ }
134
+
135
+ export function showSuccess(): void {
136
+ console.log("\n" + divider + "\n");
137
+ console.log(amber(" ✓ relay is installed in your development workflow\n"));
138
+ console.log(muted(" next steps:"));
139
+ console.log(dim(" → open Claude Code and type /validate to run your first check"));
140
+ console.log(dim(" → type /runshift-update to refresh rules as your stack evolves\n"));
141
+ console.log(muted(" connect to the runshift control plane: ") + amber("runshift.ai"));
142
+ console.log("\n" + divider + "\n");
143
+ }
144
+
145
+ export function showError(type: "network" | "rate-limit" | "validation" | "server", message?: string): void {
146
+ console.log();
147
+ switch (type) {
148
+ case "network":
149
+ console.log(amber(" could not reach relay."));
150
+ console.log(dim(" check your connection and try again.\n"));
151
+ break;
152
+ case "rate-limit":
153
+ console.log(amber(" relay rate limit reached — try again in 1 hour.\n"));
154
+ break;
155
+ case "validation":
156
+ console.log(amber(" relay could not read this repository."));
157
+ if (message) console.log(dim(` ${message}\n`));
158
+ break;
159
+ case "server":
160
+ console.log(amber(" relay encountered an error — try again or visit runshift.ai."));
161
+ if (message) console.log(dim(` ${message}\n`));
162
+ break;
163
+ }
164
+ }
@@ -0,0 +1,33 @@
1
+ import * as readline from "node:readline";
2
+
3
+ function ask(question: string): Promise<string> {
4
+ const rl = readline.createInterface({
5
+ input: process.stdin,
6
+ output: process.stdout,
7
+ });
8
+
9
+ return new Promise((resolve) => {
10
+ rl.question(question, (answer) => {
11
+ rl.close();
12
+ resolve(answer.trim());
13
+ });
14
+ });
15
+ }
16
+
17
+ export async function confirm(question: string): Promise<boolean> {
18
+ const answer = await ask(question);
19
+ const normalized = answer.toLowerCase();
20
+ return normalized === "y" || normalized === "yes";
21
+ }
22
+
23
+ export async function promptChoice(question: string): Promise<"y" | "a" | "n"> {
24
+ const answer = await ask(question);
25
+ const normalized = answer.toLowerCase();
26
+ if (normalized === "a" || normalized === "add") return "a";
27
+ if (normalized === "y" || normalized === "yes") return "y";
28
+ return "n";
29
+ }
30
+
31
+ export async function promptFilePath(question: string): Promise<string> {
32
+ return ask(question);
33
+ }
package/src/writer.ts ADDED
@@ -0,0 +1,37 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { execSync } from "node:child_process";
4
+ import type { GeneratedFile } from "./types.js";
5
+ import { showWriting, showCommit } from "./ui/display.js";
6
+
7
+ export function writeFiles(root: string, files: GeneratedFile[]): void {
8
+ for (const file of files) {
9
+ const fullPath = path.join(root, file.path);
10
+ const dir = path.dirname(fullPath);
11
+
12
+ fs.mkdirSync(dir, { recursive: true });
13
+ fs.writeFileSync(fullPath, file.content, "utf-8");
14
+ showWriting(file.path);
15
+ }
16
+ }
17
+
18
+ export function commitFiles(root: string, files: GeneratedFile[]): boolean {
19
+ try {
20
+ const filePaths = files.map((f) => `"${f.path}"`).join(" ");
21
+
22
+ execSync(`git add ${filePaths}`, {
23
+ cwd: root,
24
+ stdio: "pipe",
25
+ });
26
+
27
+ execSync(
28
+ 'git commit -m "chore: install runshift agent governance rules"',
29
+ { cwd: root, stdio: "pipe" },
30
+ );
31
+
32
+ showCommit();
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }
package/index.js DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- console.log("runshift");