zigrix 0.1.0-alpha.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.
Files changed (58) hide show
  1. package/LICENSE +184 -0
  2. package/README.md +128 -0
  3. package/dist/agents/registry.d.ts +28 -0
  4. package/dist/agents/registry.js +98 -0
  5. package/dist/config/defaults.d.ts +66 -0
  6. package/dist/config/defaults.js +58 -0
  7. package/dist/config/load.d.ts +13 -0
  8. package/dist/config/load.js +96 -0
  9. package/dist/config/mutate.d.ts +10 -0
  10. package/dist/config/mutate.js +61 -0
  11. package/dist/config/schema.d.ts +132 -0
  12. package/dist/config/schema.js +123 -0
  13. package/dist/configure.d.ts +24 -0
  14. package/dist/configure.js +164 -0
  15. package/dist/doctor.d.ts +4 -0
  16. package/dist/doctor.js +99 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +791 -0
  19. package/dist/onboard.d.ts +99 -0
  20. package/dist/onboard.js +490 -0
  21. package/dist/orchestration/dispatch.d.ts +9 -0
  22. package/dist/orchestration/dispatch.js +146 -0
  23. package/dist/orchestration/evidence.d.ts +19 -0
  24. package/dist/orchestration/evidence.js +97 -0
  25. package/dist/orchestration/finalize.d.ts +7 -0
  26. package/dist/orchestration/finalize.js +136 -0
  27. package/dist/orchestration/pipeline.d.ts +11 -0
  28. package/dist/orchestration/pipeline.js +26 -0
  29. package/dist/orchestration/report.d.ts +5 -0
  30. package/dist/orchestration/report.js +92 -0
  31. package/dist/orchestration/worker.d.ts +34 -0
  32. package/dist/orchestration/worker.js +132 -0
  33. package/dist/rules/templates.d.ts +24 -0
  34. package/dist/rules/templates.js +73 -0
  35. package/dist/runner/run.d.ts +10 -0
  36. package/dist/runner/run.js +91 -0
  37. package/dist/runner/schema.d.ts +33 -0
  38. package/dist/runner/schema.js +10 -0
  39. package/dist/runner/store.d.ts +5 -0
  40. package/dist/runner/store.js +19 -0
  41. package/dist/state/events.d.ts +3 -0
  42. package/dist/state/events.js +53 -0
  43. package/dist/state/paths.d.ts +15 -0
  44. package/dist/state/paths.js +23 -0
  45. package/dist/state/tasks.d.ts +61 -0
  46. package/dist/state/tasks.js +247 -0
  47. package/dist/state/verify.d.ts +2 -0
  48. package/dist/state/verify.js +65 -0
  49. package/examples/hello-workflow.json +13 -0
  50. package/package.json +44 -0
  51. package/skills/zigrix-doctor/SKILL.md +20 -0
  52. package/skills/zigrix-evidence/SKILL.md +21 -0
  53. package/skills/zigrix-init/SKILL.md +23 -0
  54. package/skills/zigrix-report/SKILL.md +20 -0
  55. package/skills/zigrix-shared/SKILL.md +31 -0
  56. package/skills/zigrix-task-create/SKILL.md +34 -0
  57. package/skills/zigrix-task-status/SKILL.md +27 -0
  58. package/skills/zigrix-worker/SKILL.md +22 -0
@@ -0,0 +1,61 @@
1
+ import { defaultConfig } from './defaults.js';
2
+ import { zigrixConfigSchema } from './schema.js';
3
+ function isObject(value) {
4
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
5
+ }
6
+ function clone(value) {
7
+ return structuredClone(value);
8
+ }
9
+ export function parseConfigInput(raw) {
10
+ try {
11
+ return JSON.parse(raw);
12
+ }
13
+ catch {
14
+ return raw;
15
+ }
16
+ }
17
+ export function getValueAtPath(value, dottedPath) {
18
+ if (!dottedPath)
19
+ return value;
20
+ return dottedPath.split('.').reduce((acc, key) => {
21
+ if (acc && typeof acc === 'object' && key in acc) {
22
+ return acc[key];
23
+ }
24
+ return undefined;
25
+ }, value);
26
+ }
27
+ export function setValueAtPath(target, dottedPath, nextValue) {
28
+ const root = clone(target);
29
+ const keys = dottedPath.split('.').filter(Boolean);
30
+ if (keys.length === 0) {
31
+ return clone(nextValue);
32
+ }
33
+ let cursor = root;
34
+ for (const key of keys.slice(0, -1)) {
35
+ const current = cursor[key];
36
+ if (!isObject(current)) {
37
+ cursor[key] = {};
38
+ }
39
+ cursor = cursor[key];
40
+ }
41
+ cursor[keys.at(-1)] = clone(nextValue);
42
+ return root;
43
+ }
44
+ export function resetValueAtPath(config, dottedPath) {
45
+ const defaults = clone(defaultConfig);
46
+ if (!dottedPath || dottedPath === 'all') {
47
+ return zigrixConfigSchema.parse(defaults);
48
+ }
49
+ const defaultValue = getValueAtPath(defaults, dottedPath);
50
+ if (defaultValue === undefined) {
51
+ throw new Error(`default path not found: ${dottedPath}`);
52
+ }
53
+ return zigrixConfigSchema.parse(setValueAtPath(config, dottedPath, defaultValue));
54
+ }
55
+ export function diffValues(current, baseline) {
56
+ return {
57
+ changed: JSON.stringify(current) !== JSON.stringify(baseline),
58
+ current,
59
+ baseline,
60
+ };
61
+ }
@@ -0,0 +1,132 @@
1
+ import { z } from 'zod';
2
+ export declare const zigrixConfigSchema: z.ZodObject<{
3
+ paths: z.ZodObject<{
4
+ baseDir: z.ZodString;
5
+ tasksDir: z.ZodString;
6
+ evidenceDir: z.ZodString;
7
+ promptsDir: z.ZodString;
8
+ eventsFile: z.ZodString;
9
+ indexFile: z.ZodString;
10
+ runsDir: z.ZodString;
11
+ rulesDir: z.ZodString;
12
+ }, z.core.$strip>;
13
+ workspace: z.ZodObject<{
14
+ projectsBaseDir: z.ZodDefault<z.ZodString>;
15
+ }, z.core.$strip>;
16
+ agents: z.ZodObject<{
17
+ registry: z.ZodRecord<z.ZodString, z.ZodObject<{
18
+ label: z.ZodString;
19
+ role: z.ZodString;
20
+ runtime: z.ZodString;
21
+ enabled: z.ZodDefault<z.ZodBoolean>;
22
+ metadata: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
23
+ }, z.core.$strip>>;
24
+ orchestration: z.ZodObject<{
25
+ participants: z.ZodArray<z.ZodString>;
26
+ excluded: z.ZodArray<z.ZodString>;
27
+ }, z.core.$strip>;
28
+ }, z.core.$strip>;
29
+ rules: z.ZodObject<{
30
+ scales: z.ZodRecord<z.ZodString, z.ZodObject<{
31
+ requiredRoles: z.ZodArray<z.ZodString>;
32
+ optionalRoles: z.ZodArray<z.ZodString>;
33
+ }, z.core.$strip>>;
34
+ completion: z.ZodObject<{
35
+ requireQa: z.ZodBoolean;
36
+ requireEvidence: z.ZodBoolean;
37
+ requireUserReport: z.ZodBoolean;
38
+ }, z.core.$strip>;
39
+ stale: z.ZodObject<{
40
+ defaultHours: z.ZodNumber;
41
+ }, z.core.$strip>;
42
+ }, z.core.$strip>;
43
+ templates: z.ZodObject<{
44
+ workerPrompt: z.ZodObject<{
45
+ format: z.ZodDefault<z.ZodEnum<{
46
+ markdown: "markdown";
47
+ text: "text";
48
+ }>>;
49
+ version: z.ZodNumber;
50
+ placeholders: z.ZodArray<z.ZodString>;
51
+ body: z.ZodString;
52
+ }, z.core.$strip>;
53
+ finalReport: z.ZodObject<{
54
+ format: z.ZodDefault<z.ZodEnum<{
55
+ markdown: "markdown";
56
+ text: "text";
57
+ }>>;
58
+ version: z.ZodNumber;
59
+ placeholders: z.ZodArray<z.ZodString>;
60
+ body: z.ZodString;
61
+ }, z.core.$strip>;
62
+ }, z.core.$strip>;
63
+ runtime: z.ZodObject<{
64
+ outputMode: z.ZodEnum<{
65
+ text: "text";
66
+ json: "json";
67
+ }>;
68
+ jsonIndent: z.ZodNumber;
69
+ }, z.core.$strip>;
70
+ }, z.core.$strip>;
71
+ export type ZigrixConfig = z.infer<typeof zigrixConfigSchema>;
72
+ export declare const zigrixConfigJsonSchema: {
73
+ readonly $schema: "https://json-schema.org/draft/2020-12/schema";
74
+ readonly title: "ZigrixConfig";
75
+ readonly type: "object";
76
+ readonly required: readonly ["paths", "workspace", "agents", "rules", "templates", "runtime"];
77
+ readonly properties: {
78
+ readonly paths: {
79
+ readonly type: "object";
80
+ readonly required: readonly ["baseDir", "tasksDir", "evidenceDir", "promptsDir", "eventsFile", "indexFile", "runsDir", "rulesDir"];
81
+ readonly properties: {
82
+ readonly baseDir: {
83
+ readonly type: "string";
84
+ };
85
+ readonly tasksDir: {
86
+ readonly type: "string";
87
+ };
88
+ readonly evidenceDir: {
89
+ readonly type: "string";
90
+ };
91
+ readonly promptsDir: {
92
+ readonly type: "string";
93
+ };
94
+ readonly eventsFile: {
95
+ readonly type: "string";
96
+ };
97
+ readonly indexFile: {
98
+ readonly type: "string";
99
+ };
100
+ readonly runsDir: {
101
+ readonly type: "string";
102
+ };
103
+ readonly rulesDir: {
104
+ readonly type: "string";
105
+ };
106
+ };
107
+ readonly additionalProperties: false;
108
+ };
109
+ readonly workspace: {
110
+ readonly type: "object";
111
+ readonly properties: {
112
+ readonly projectsBaseDir: {
113
+ readonly type: "string";
114
+ };
115
+ };
116
+ readonly additionalProperties: false;
117
+ };
118
+ readonly agents: {
119
+ readonly type: "object";
120
+ };
121
+ readonly rules: {
122
+ readonly type: "object";
123
+ };
124
+ readonly templates: {
125
+ readonly type: "object";
126
+ };
127
+ readonly runtime: {
128
+ readonly type: "object";
129
+ };
130
+ };
131
+ readonly additionalProperties: false;
132
+ };
@@ -0,0 +1,123 @@
1
+ import { z } from 'zod';
2
+ const roleSchema = z.string().min(1);
3
+ const pathSchema = z.string().min(1);
4
+ const agentSchema = z.object({
5
+ label: z.string().min(1),
6
+ role: z.string().min(1),
7
+ runtime: z.string().min(1),
8
+ enabled: z.boolean().default(true),
9
+ metadata: z.record(z.string(), z.unknown()).default({}),
10
+ });
11
+ const templateSchema = z.object({
12
+ format: z.enum(['markdown', 'text']).default('markdown'),
13
+ version: z.number().int().positive(),
14
+ placeholders: z.array(z.string().min(1)).min(1),
15
+ body: z.string().min(1),
16
+ });
17
+ export const zigrixConfigSchema = z.object({
18
+ paths: z.object({
19
+ baseDir: pathSchema,
20
+ tasksDir: pathSchema,
21
+ evidenceDir: pathSchema,
22
+ promptsDir: pathSchema,
23
+ eventsFile: pathSchema,
24
+ indexFile: pathSchema,
25
+ runsDir: pathSchema,
26
+ rulesDir: pathSchema,
27
+ }),
28
+ workspace: z.object({
29
+ projectsBaseDir: z.string().default(''),
30
+ }),
31
+ agents: z.object({
32
+ registry: z.record(z.string(), agentSchema),
33
+ orchestration: z.object({
34
+ participants: z.array(z.string()),
35
+ excluded: z.array(z.string()),
36
+ }),
37
+ }).superRefine((value, ctx) => {
38
+ const overlap = value.orchestration.participants.filter((item) => value.orchestration.excluded.includes(item));
39
+ for (const agentId of overlap) {
40
+ ctx.addIssue({
41
+ code: z.ZodIssueCode.custom,
42
+ message: `agent '${agentId}' cannot be both participant and excluded`,
43
+ path: ['orchestration', 'excluded'],
44
+ });
45
+ }
46
+ const knownAgents = new Set(Object.keys(value.registry));
47
+ for (const agentId of value.orchestration.participants) {
48
+ if (!knownAgents.has(agentId)) {
49
+ ctx.addIssue({
50
+ code: z.ZodIssueCode.custom,
51
+ message: `participant '${agentId}' must exist in registry`,
52
+ path: ['orchestration', 'participants'],
53
+ });
54
+ }
55
+ }
56
+ for (const agentId of value.orchestration.excluded) {
57
+ if (!knownAgents.has(agentId)) {
58
+ ctx.addIssue({
59
+ code: z.ZodIssueCode.custom,
60
+ message: `excluded agent '${agentId}' must exist in registry`,
61
+ path: ['orchestration', 'excluded'],
62
+ });
63
+ }
64
+ }
65
+ }),
66
+ rules: z.object({
67
+ scales: z.record(z.string(), z.object({
68
+ requiredRoles: z.array(roleSchema),
69
+ optionalRoles: z.array(roleSchema),
70
+ })),
71
+ completion: z.object({
72
+ requireQa: z.boolean(),
73
+ requireEvidence: z.boolean(),
74
+ requireUserReport: z.boolean(),
75
+ }),
76
+ stale: z.object({
77
+ defaultHours: z.number().positive(),
78
+ }),
79
+ }),
80
+ templates: z.object({
81
+ workerPrompt: templateSchema,
82
+ finalReport: templateSchema,
83
+ }),
84
+ runtime: z.object({
85
+ outputMode: z.enum(['text', 'json']),
86
+ jsonIndent: z.number().int().min(0).max(8),
87
+ }),
88
+ });
89
+ export const zigrixConfigJsonSchema = {
90
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
91
+ title: 'ZigrixConfig',
92
+ type: 'object',
93
+ required: ['paths', 'workspace', 'agents', 'rules', 'templates', 'runtime'],
94
+ properties: {
95
+ paths: {
96
+ type: 'object',
97
+ required: ['baseDir', 'tasksDir', 'evidenceDir', 'promptsDir', 'eventsFile', 'indexFile', 'runsDir', 'rulesDir'],
98
+ properties: {
99
+ baseDir: { type: 'string' },
100
+ tasksDir: { type: 'string' },
101
+ evidenceDir: { type: 'string' },
102
+ promptsDir: { type: 'string' },
103
+ eventsFile: { type: 'string' },
104
+ indexFile: { type: 'string' },
105
+ runsDir: { type: 'string' },
106
+ rulesDir: { type: 'string' },
107
+ },
108
+ additionalProperties: false,
109
+ },
110
+ workspace: {
111
+ type: 'object',
112
+ properties: {
113
+ projectsBaseDir: { type: 'string' },
114
+ },
115
+ additionalProperties: false,
116
+ },
117
+ agents: { type: 'object' },
118
+ rules: { type: 'object' },
119
+ templates: { type: 'object' },
120
+ runtime: { type: 'object' },
121
+ },
122
+ additionalProperties: false,
123
+ };
@@ -0,0 +1,24 @@
1
+ import { type PathStabilizeResult, type SkillRegistrationResult } from './onboard.js';
2
+ export interface ConfigureResult {
3
+ ok: boolean;
4
+ action: string;
5
+ configPath: string;
6
+ sections: string[];
7
+ agentsRegistered: string[];
8
+ agentsRemoved: string[];
9
+ agentsSkipped: string[];
10
+ rulesCopied: string[];
11
+ rulesSkipped: string[];
12
+ skillsResult: SkillRegistrationResult | null;
13
+ pathResult: PathStabilizeResult | null;
14
+ workspaceChanged: boolean;
15
+ warnings: string[];
16
+ }
17
+ export interface RunConfigureOptions {
18
+ sections?: string[];
19
+ yes?: boolean;
20
+ projectDir?: string;
21
+ projectsBaseDir?: string;
22
+ silent?: boolean;
23
+ }
24
+ export declare function runConfigure(options: RunConfigureOptions): Promise<ConfigureResult>;
@@ -0,0 +1,164 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { removeAgent } from './agents/registry.js';
5
+ import { loadConfig, writeConfigFile } from './config/load.js';
6
+ import { detectOpenClawHome, ensureZigrixInPath, filterAgents, loadOpenClawConfig, promptAgentSelection, registerAgents, registerSkills, seedRules, } from './onboard.js';
7
+ import { resolvePaths } from './state/paths.js';
8
+ const ALL_SECTIONS = ['agents', 'rules', 'workspace', 'path', 'skills'];
9
+ // ─── Configure ────────────────────────────────────────────────────────────────
10
+ export async function runConfigure(options) {
11
+ const warnings = [];
12
+ const silent = options.silent ?? false;
13
+ const log = (msg) => { if (!silent)
14
+ console.log(msg); };
15
+ // Resolve base dir at runtime (not from the static ZIGRIX_HOME constant)
16
+ const runtimeBaseDir = process.env.ZIGRIX_HOME
17
+ ? path.resolve(process.env.ZIGRIX_HOME)
18
+ : path.join(os.homedir(), '.zigrix');
19
+ const loaded = loadConfig({ baseDir: runtimeBaseDir });
20
+ if (!loaded.configPath || !fs.existsSync(loaded.configPath)) {
21
+ throw new Error('zigrix not initialized. Run `zigrix onboard` first.');
22
+ }
23
+ let config = structuredClone(loaded.config);
24
+ const configPath = loaded.configPath;
25
+ const paths = resolvePaths(config);
26
+ const sections = options.sections && options.sections.length > 0
27
+ ? options.sections
28
+ : [...ALL_SECTIONS];
29
+ let agentsRegistered = [];
30
+ let agentsRemoved = [];
31
+ let agentsSkipped = [];
32
+ let rulesCopied = [];
33
+ let rulesSkipped = [];
34
+ let skillsResult = null;
35
+ let pathResult = null;
36
+ let workspaceChanged = false;
37
+ let configDirty = false;
38
+ // ─── agents ───────────────────────────────────────────────────────────
39
+ if (sections.includes('agents')) {
40
+ const openclawHome = detectOpenClawHome();
41
+ const openclawConfig = loadOpenClawConfig(openclawHome);
42
+ if (openclawConfig) {
43
+ const allAgents = filterAgents(openclawConfig.agents?.list ?? []);
44
+ let selectedAgents;
45
+ if (options.yes) {
46
+ selectedAgents = allAgents;
47
+ }
48
+ else {
49
+ log('\n── Agent Configuration ──');
50
+ selectedAgents = await promptAgentSelection(allAgents);
51
+ }
52
+ if (selectedAgents.length > 0) {
53
+ const result = registerAgents(config, selectedAgents);
54
+ config = result.config;
55
+ agentsRegistered = result.registered;
56
+ agentsSkipped = result.skipped;
57
+ if (result.registered.length > 0)
58
+ configDirty = true;
59
+ }
60
+ // Remove agents not in selection (unless --yes which means "keep all")
61
+ if (!options.yes && allAgents.length > 0) {
62
+ const selectedIds = new Set(selectedAgents.map((a) => a.id));
63
+ for (const agent of allAgents) {
64
+ if (!selectedIds.has(agent.id) && config.agents.registry[agent.id]) {
65
+ const result = removeAgent(config, agent.id);
66
+ config = result.config;
67
+ agentsRemoved.push(agent.id);
68
+ configDirty = true;
69
+ }
70
+ }
71
+ }
72
+ if (agentsRegistered.length > 0)
73
+ log(`✅ Registered: ${agentsRegistered.join(', ')}`);
74
+ if (agentsRemoved.length > 0)
75
+ log(`🗑️ Removed: ${agentsRemoved.join(', ')}`);
76
+ if (agentsSkipped.length > 0)
77
+ log(`⏭️ Unchanged: ${agentsSkipped.join(', ')}`);
78
+ }
79
+ else {
80
+ warnings.push('OpenClaw config not found — agent reconfiguration skipped.');
81
+ log(`⚠️ ${warnings[warnings.length - 1]}`);
82
+ }
83
+ }
84
+ // ─── workspace ────────────────────────────────────────────────────────
85
+ if (sections.includes('workspace') && options.projectsBaseDir) {
86
+ const resolved = path.resolve(options.projectsBaseDir);
87
+ if (config.workspace.projectsBaseDir !== resolved) {
88
+ config.workspace.projectsBaseDir = resolved;
89
+ configDirty = true;
90
+ workspaceChanged = true;
91
+ log(`✅ Projects base dir set to: ${resolved}`);
92
+ }
93
+ }
94
+ // ─── rules ────────────────────────────────────────────────────────────
95
+ if (sections.includes('rules')) {
96
+ const projectDir = options.projectDir ?? process.cwd();
97
+ const rulesSourceDir = path.join(projectDir, 'orchestration', 'rules');
98
+ if (fs.existsSync(rulesSourceDir)) {
99
+ const result = seedRules(rulesSourceDir, paths.rulesDir);
100
+ rulesCopied = result.copied;
101
+ rulesSkipped = result.skipped;
102
+ if (rulesCopied.length > 0)
103
+ log(`✅ Rules added: ${rulesCopied.join(', ')}`);
104
+ if (rulesSkipped.length > 0)
105
+ log(`⏭️ Rules unchanged: ${rulesSkipped.join(', ')}`);
106
+ }
107
+ else {
108
+ log(`ℹ️ No orchestration/rules/ at ${projectDir} — rule seeding skipped.`);
109
+ }
110
+ }
111
+ // ─── path ─────────────────────────────────────────────────────────────
112
+ if (sections.includes('path')) {
113
+ pathResult = ensureZigrixInPath();
114
+ if (pathResult.symlinkCreated) {
115
+ log(`✅ zigrix symlinked to ${pathResult.symlinkPath}`);
116
+ }
117
+ else if (pathResult.alreadyInPath) {
118
+ log('✅ zigrix already in PATH');
119
+ }
120
+ if (pathResult.warning) {
121
+ warnings.push(pathResult.warning);
122
+ log(`⚠️ ${pathResult.warning}`);
123
+ }
124
+ }
125
+ // ─── skills ───────────────────────────────────────────────────────────
126
+ if (sections.includes('skills')) {
127
+ const openclawHome = detectOpenClawHome();
128
+ if (fs.existsSync(openclawHome)) {
129
+ skillsResult = registerSkills(openclawHome);
130
+ if (skillsResult.registered.length > 0) {
131
+ log(`✅ Skills registered: ${skillsResult.registered.join(', ')}`);
132
+ }
133
+ if (skillsResult.skipped.length > 0) {
134
+ log(`⏭️ Skills unchanged: ${skillsResult.skipped.join(', ')}`);
135
+ }
136
+ for (const f of skillsResult.failed) {
137
+ warnings.push(`Skill failed: ${f}`);
138
+ log(`⚠️ ${warnings[warnings.length - 1]}`);
139
+ }
140
+ }
141
+ else {
142
+ log('ℹ️ OpenClaw not detected — skill registration skipped.');
143
+ }
144
+ }
145
+ // ─── persist ──────────────────────────────────────────────────────────
146
+ if (configDirty) {
147
+ writeConfigFile(configPath, config);
148
+ }
149
+ return {
150
+ ok: true,
151
+ action: 'configure',
152
+ configPath,
153
+ sections,
154
+ agentsRegistered,
155
+ agentsRemoved,
156
+ agentsSkipped,
157
+ rulesCopied,
158
+ rulesSkipped,
159
+ skillsResult,
160
+ pathResult,
161
+ workspaceChanged,
162
+ warnings,
163
+ };
164
+ }
@@ -0,0 +1,4 @@
1
+ import type { LoadedConfig } from './config/load.js';
2
+ import type { ZigrixPaths } from './state/paths.js';
3
+ export declare function gatherDoctor(loaded: LoadedConfig, paths: ZigrixPaths): Record<string, unknown>;
4
+ export declare function renderDoctorText(payload: Record<string, any>): string;
package/dist/doctor.js ADDED
@@ -0,0 +1,99 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ function existsWritable(targetPath) {
4
+ try {
5
+ return fs.existsSync(targetPath) ? fs.accessSync(targetPath, fs.constants.W_OK) === undefined : fs.accessSync(path.dirname(targetPath), fs.constants.W_OK) === undefined;
6
+ }
7
+ catch {
8
+ return false;
9
+ }
10
+ }
11
+ function detectOpenClawHome() {
12
+ return process.env.OPENCLAW_HOME ? path.resolve(process.env.OPENCLAW_HOME) : path.join(process.env.HOME ?? '~', '.openclaw');
13
+ }
14
+ export function gatherDoctor(loaded, paths) {
15
+ const openclawHome = detectOpenClawHome();
16
+ const openclawSkillsDir = path.join(openclawHome, 'skills');
17
+ const warnings = [];
18
+ const rulesDir = paths.rulesDir;
19
+ const ruleFiles = fs.existsSync(rulesDir)
20
+ ? fs.readdirSync(rulesDir).filter((f) => f.endsWith('.md')).sort()
21
+ : [];
22
+ const payload = {
23
+ node: {
24
+ executable: process.execPath,
25
+ version: process.versions.node,
26
+ ok: Number(process.versions.node.split('.')[0]) >= 22,
27
+ },
28
+ paths: {
29
+ baseDir: loaded.baseDir,
30
+ configPath: loaded.configPath,
31
+ tasksDir: paths.tasksDir,
32
+ promptsDir: paths.promptsDir,
33
+ evidenceDir: paths.evidenceDir,
34
+ runsDir: paths.runsDir,
35
+ rulesDir: paths.rulesDir,
36
+ },
37
+ files: {
38
+ configExists: loaded.configPath ? fs.existsSync(loaded.configPath) : false,
39
+ baseDirExists: fs.existsSync(paths.baseDir),
40
+ indexExists: fs.existsSync(paths.indexFile),
41
+ eventsExists: fs.existsSync(paths.eventsFile),
42
+ },
43
+ rules: {
44
+ dir: rulesDir,
45
+ exists: fs.existsSync(rulesDir),
46
+ files: ruleFiles,
47
+ count: ruleFiles.length,
48
+ },
49
+ writeAccess: {
50
+ baseDir: existsWritable(paths.baseDir),
51
+ configPath: loaded.configPath ? existsWritable(loaded.configPath) : existsWritable(path.join(paths.baseDir, 'zigrix.config.json')),
52
+ },
53
+ binaries: {
54
+ node: process.execPath,
55
+ npm: process.env.npm_execpath ?? null,
56
+ openclaw: null,
57
+ },
58
+ openclaw: {
59
+ home: openclawHome,
60
+ exists: fs.existsSync(openclawHome),
61
+ skillsDir: openclawSkillsDir,
62
+ skillsDirExists: fs.existsSync(openclawSkillsDir),
63
+ },
64
+ };
65
+ if (!payload.node.ok)
66
+ warnings.push('Node.js 22+ is required.');
67
+ if (!payload.files.configExists)
68
+ warnings.push('zigrix.config.json not found. Run `zigrix onboard`.');
69
+ if (!payload.files.baseDirExists)
70
+ warnings.push('~/.zigrix not found. Run `zigrix onboard`.');
71
+ if (!payload.writeAccess.baseDir)
72
+ warnings.push('Base directory is not writable.');
73
+ if (!payload.openclaw.exists)
74
+ warnings.push('~/.openclaw not found. OpenClaw integration remains optional.');
75
+ if (payload.rules.count === 0)
76
+ warnings.push('No rule files found in rules directory. Seed from orchestration/rules/.');
77
+ return {
78
+ ...payload,
79
+ summary: {
80
+ ready: payload.node.ok && payload.writeAccess.baseDir && payload.files.configExists,
81
+ warnings,
82
+ },
83
+ };
84
+ }
85
+ export function renderDoctorText(payload) {
86
+ const lines = [
87
+ 'Zigrix Doctor',
88
+ `- Node: ${payload.node.version} (${payload.node.ok ? 'ok' : 'too old'})`,
89
+ `- Base dir: ${payload.paths.baseDir}`,
90
+ `- Config: ${payload.paths.configPath ?? 'missing'}`,
91
+ `- Rules dir: ${payload.rules.dir} (${payload.rules.count} files)`,
92
+ `- OpenClaw home: ${payload.openclaw.home} (${payload.openclaw.exists ? 'present' : 'missing'})`,
93
+ `- Ready: ${payload.summary.ready ? 'yes' : 'no'}`,
94
+ ];
95
+ for (const warning of payload.summary.warnings) {
96
+ lines.push(`- Warning: ${warning}`);
97
+ }
98
+ return lines.join('\n');
99
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};