@josfox/jos 4.0.2 → 4.0.6
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/LICENSE +21 -0
- package/README.md +93 -195
- package/bin/jos +9 -6
- package/package.json +3 -3
- package/src/commands/add.js +145 -0
- package/src/commands/init.js +162 -0
- package/src/commands/run.js +172 -153
- package/src/commands/validate.js +190 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JOS INIT Command - Create new .jos artifacts
|
|
3
|
+
* Creates a minimal valid .jos with detached integrity manifest
|
|
4
|
+
* Format version v0.0.7 — Specification maturity v0.1.0 (Alpha)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
|
|
11
|
+
const C = {
|
|
12
|
+
reset: '\x1b[0m', bold: '\x1b[1m',
|
|
13
|
+
purple: '\x1b[38;5;135m', cyan: '\x1b[38;5;51m',
|
|
14
|
+
green: '\x1b[38;5;78m', gray: '\x1b[38;5;245m'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
exports.execute = async (args, home) => {
|
|
18
|
+
const name = args[0] || 'new-artifact';
|
|
19
|
+
const type = args.includes('--pipeline') ? 'pipeline' : 'atom';
|
|
20
|
+
const filename = `${name}.jos`;
|
|
21
|
+
|
|
22
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
23
|
+
console.log(`
|
|
24
|
+
${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
|
|
25
|
+
${C.bold}JOS INIT${C.reset} // Create new .jos artifacts
|
|
26
|
+
${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
|
|
27
|
+
|
|
28
|
+
${C.bold}Usage:${C.reset}
|
|
29
|
+
jos init <name> [options]
|
|
30
|
+
|
|
31
|
+
${C.bold}Options:${C.reset}
|
|
32
|
+
--pipeline Create a pipeline artifact (default: atom)
|
|
33
|
+
|
|
34
|
+
${C.bold}Creates:${C.reset}
|
|
35
|
+
<name>.jos The artifact file
|
|
36
|
+
<name>.jos.sig.json Detached integrity manifest
|
|
37
|
+
`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(`\n${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}`);
|
|
42
|
+
console.log(`${C.bold}JOS INIT${C.reset} // ${C.gray}Creating ${type} artifact${C.reset}`);
|
|
43
|
+
console.log(`${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`);
|
|
44
|
+
|
|
45
|
+
// Create artifact with full JOSFOXAI + MAGIC compliance
|
|
46
|
+
const template = {
|
|
47
|
+
"$schema": "https://josfox.ai/schemas/jos-0.0.7.json",
|
|
48
|
+
"$comment": "Format version v0.0.7 — Specification maturity v0.1.0 (Alpha)",
|
|
49
|
+
|
|
50
|
+
// JOSFOXAI Kernel
|
|
51
|
+
"jos": {
|
|
52
|
+
"open": `jos run ${type}`,
|
|
53
|
+
"supports": ["@josfox/jos", "josctl"],
|
|
54
|
+
"canonical": "Format version v0.0.7 — Specification maturity v0.1.0 (Alpha)"
|
|
55
|
+
},
|
|
56
|
+
"orchestration_contract": {
|
|
57
|
+
"version": "0.0.7",
|
|
58
|
+
"mode": "sync"
|
|
59
|
+
},
|
|
60
|
+
"id_jos": `${type}-${name}-${Date.now()}`,
|
|
61
|
+
"adaptive_ai": {
|
|
62
|
+
"learning_mode": "frozen",
|
|
63
|
+
"llm_friendly_sentence": `This ${type} artifact performs the ${name} task.`
|
|
64
|
+
},
|
|
65
|
+
"security": {
|
|
66
|
+
"type": "open",
|
|
67
|
+
"permissions": [],
|
|
68
|
+
"health_check": null,
|
|
69
|
+
"integrity": null,
|
|
70
|
+
"integrity_ref": `./${filename}.sig.json`
|
|
71
|
+
},
|
|
72
|
+
"files": [],
|
|
73
|
+
"orchestration": {
|
|
74
|
+
"state": { "current_status": "idle" },
|
|
75
|
+
"definitions": {
|
|
76
|
+
"main_task": {
|
|
77
|
+
"type": "shell",
|
|
78
|
+
"description": "Main task - replace with your command",
|
|
79
|
+
"command": "echo 'Hello from " + name + "'"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"flows": {
|
|
83
|
+
"main": {
|
|
84
|
+
"description": "Main execution flow",
|
|
85
|
+
"steps": ["main_task"]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"x_run_params": {
|
|
90
|
+
"timeout": 300,
|
|
91
|
+
"retry_on_failure": false
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// MAGIC Kernel
|
|
95
|
+
"meta": {
|
|
96
|
+
"version": "1.0.0",
|
|
97
|
+
"type": type,
|
|
98
|
+
"name": name,
|
|
99
|
+
"provider": "local",
|
|
100
|
+
"author": "JOS Developer",
|
|
101
|
+
"canonical_version": "Format version v0.0.7 — Specification maturity v0.1.0 (Alpha)"
|
|
102
|
+
},
|
|
103
|
+
"artifacts": {
|
|
104
|
+
"description": `Auto-generated ${type} artifact. Update this description.`,
|
|
105
|
+
"outputs": []
|
|
106
|
+
},
|
|
107
|
+
"guardrails": {
|
|
108
|
+
"avoid": [],
|
|
109
|
+
"max_retries": 1
|
|
110
|
+
},
|
|
111
|
+
"intention": {
|
|
112
|
+
"objective": "Define your objective here",
|
|
113
|
+
"success_criteria": "Define success criteria here"
|
|
114
|
+
},
|
|
115
|
+
"capabilities": []
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Write artifact
|
|
119
|
+
const content = JSON.stringify(template, null, 2);
|
|
120
|
+
fs.writeFileSync(filename, content);
|
|
121
|
+
console.log(`${C.green}✓ Created:${C.reset} ${filename}`);
|
|
122
|
+
|
|
123
|
+
// Generate integrity manifest
|
|
124
|
+
const sha256 = crypto.createHash('sha256').update(content).digest('hex');
|
|
125
|
+
const stats = fs.statSync(filename);
|
|
126
|
+
|
|
127
|
+
const manifest = {
|
|
128
|
+
"$schema": "https://josfox.ai/schemas/jos-manifest-0.0.7.json",
|
|
129
|
+
"manifest_version": "1.0.0",
|
|
130
|
+
"artifact_ref": filename,
|
|
131
|
+
"artifact_sha256": sha256,
|
|
132
|
+
"artifact_size_bytes": stats.size,
|
|
133
|
+
"timestamp": new Date().toISOString(),
|
|
134
|
+
"generated_by": "jos init command",
|
|
135
|
+
"canonical_version": "Format version v0.0.7 — Specification maturity v0.1.0 (Alpha)"
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const manifestPath = filename + '.sig.json';
|
|
139
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
140
|
+
console.log(`${C.green}✓ Created:${C.reset} ${manifestPath}`);
|
|
141
|
+
console.log(`${C.gray} SHA256: ${sha256}${C.reset}`);
|
|
142
|
+
|
|
143
|
+
console.log(`
|
|
144
|
+
${C.cyan}Next steps:${C.reset}
|
|
145
|
+
1. Edit ${filename} to define your tasks
|
|
146
|
+
2. Run: ${C.purple}jos validate ${filename}${C.reset}
|
|
147
|
+
3. Run: ${C.purple}jos run ${filename}${C.reset}
|
|
148
|
+
`);
|
|
149
|
+
|
|
150
|
+
// Log to witness
|
|
151
|
+
const runDir = path.join(home, 'runs', 'init-' + new Date().toISOString().replace(/[:.]/g, '-'));
|
|
152
|
+
if (!fs.existsSync(runDir)) fs.mkdirSync(runDir, { recursive: true });
|
|
153
|
+
|
|
154
|
+
const event = {
|
|
155
|
+
timestamp: new Date().toISOString(),
|
|
156
|
+
type: 'init:created',
|
|
157
|
+
artifact: filename,
|
|
158
|
+
manifest: manifestPath,
|
|
159
|
+
sha256: sha256
|
|
160
|
+
};
|
|
161
|
+
fs.appendFileSync(path.join(runDir, 'events.jsonl'), JSON.stringify(event) + '\n');
|
|
162
|
+
};
|
package/src/commands/run.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* JOS RUN Command - Execute .jos artifacts
|
|
3
3
|
* Complies with JOSFOXAI MAGIC contract
|
|
4
|
+
* Integrity Strategy: Detached Manifest (.sig.json)
|
|
5
|
+
* Format version v0.0.7 — Specification maturity v0.1.0 (Alpha)
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
const fs = require('fs');
|
|
@@ -11,215 +13,232 @@ const crypto = require('crypto');
|
|
|
11
13
|
// AURORA colors
|
|
12
14
|
const C = {
|
|
13
15
|
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
14
|
-
purple: '\x1b[38;5;135m',
|
|
15
|
-
green: '\x1b[38;5;78m', red: '\x1b[38;5;196m',
|
|
16
|
-
|
|
16
|
+
purple: '\x1b[38;5;135m', cyan: '\x1b[38;5;51m',
|
|
17
|
+
green: '\x1b[38;5;78m', red: '\x1b[38;5;196m',
|
|
18
|
+
yellow: '\x1b[38;5;220m', white: '\x1b[38;5;255m',
|
|
19
|
+
gray: '\x1b[38;5;245m', blue: '\x1b[38;5;39m'
|
|
17
20
|
};
|
|
18
21
|
|
|
22
|
+
// Witness Logging
|
|
23
|
+
function logWitness(home, eventType, data) {
|
|
24
|
+
const runId = global.JOS_RUN_ID || new Date().toISOString().replace(/[:.]/g, '-');
|
|
25
|
+
global.JOS_RUN_ID = runId;
|
|
26
|
+
|
|
27
|
+
const runDir = path.join(home, 'runs', runId);
|
|
28
|
+
if (!fs.existsSync(runDir)) fs.mkdirSync(runDir, { recursive: true });
|
|
29
|
+
|
|
30
|
+
const event = {
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
type: `run:${eventType}`,
|
|
33
|
+
...data
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
fs.appendFileSync(path.join(runDir, 'events.jsonl'), JSON.stringify(event) + '\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
19
39
|
// Compute SHA-256 integrity
|
|
20
40
|
function computeIntegrity(content) {
|
|
21
41
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
22
42
|
}
|
|
23
43
|
|
|
24
|
-
//
|
|
25
|
-
function
|
|
26
|
-
const
|
|
27
|
-
|
|
44
|
+
// Verify Integrity using Detached Manifest
|
|
45
|
+
function verifyIntegrity(artifactPath, content, artifact) {
|
|
46
|
+
const integrityRef = artifact.security?.integrity_ref;
|
|
47
|
+
if (!integrityRef) return { valid: true, warning: "open (no integrity_ref)" };
|
|
28
48
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
49
|
+
const manifestPath = path.resolve(path.dirname(artifactPath), integrityRef);
|
|
50
|
+
if (!fs.existsSync(manifestPath)) {
|
|
51
|
+
return { valid: false, error: `Manifest not found at ${manifestPath}` };
|
|
52
|
+
}
|
|
33
53
|
|
|
34
|
-
|
|
35
|
-
|
|
54
|
+
let manifest;
|
|
55
|
+
try {
|
|
56
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return { valid: false, error: "Invalid manifest JSON" };
|
|
59
|
+
}
|
|
36
60
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
61
|
+
const actualHash = computeIntegrity(content);
|
|
62
|
+
if (actualHash !== manifest.artifact_sha256) {
|
|
63
|
+
return {
|
|
64
|
+
valid: false,
|
|
65
|
+
error: `Hash mismatch. Expected: ${manifest.artifact_sha256.substring(0, 16)}..., Got: ${actualHash.substring(0, 16)}...`
|
|
66
|
+
};
|
|
67
|
+
}
|
|
41
68
|
|
|
42
|
-
return { valid:
|
|
69
|
+
return { valid: true, verified: true, hash: actualHash };
|
|
43
70
|
}
|
|
44
71
|
|
|
45
72
|
exports.execute = async (args, home) => {
|
|
46
73
|
const target = args[0];
|
|
47
74
|
const dryRun = args.includes('--dry-run');
|
|
48
|
-
const
|
|
75
|
+
const insecure = args.includes('--insecure');
|
|
49
76
|
|
|
50
|
-
if (
|
|
77
|
+
if (args.includes('--help') || !target) {
|
|
51
78
|
console.log(`
|
|
52
|
-
${C.
|
|
79
|
+
${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
|
|
80
|
+
${C.bold}JOS RUN${C.reset} // Execute .jos artifacts
|
|
81
|
+
${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
|
|
53
82
|
|
|
54
|
-
${C.
|
|
83
|
+
${C.bold}Usage:${C.reset}
|
|
84
|
+
jos run <file.jos> [options]
|
|
55
85
|
|
|
56
|
-
${C.
|
|
57
|
-
--
|
|
58
|
-
--
|
|
59
|
-
--
|
|
86
|
+
${C.bold}Options:${C.reset}
|
|
87
|
+
--flow <name> Flow to execute (default: main)
|
|
88
|
+
--dry-run Simulate without executing
|
|
89
|
+
--insecure Skip integrity verification
|
|
60
90
|
|
|
61
|
-
${C.
|
|
62
|
-
jos run
|
|
63
|
-
jos run ./my-artifact.jos --dry-run
|
|
64
|
-
jos run artifact.jos --task build
|
|
91
|
+
${C.bold}Example:${C.reset}
|
|
92
|
+
jos run orchestration.jos --flow publish_all --dry-run
|
|
65
93
|
`);
|
|
66
94
|
return;
|
|
67
95
|
}
|
|
68
96
|
|
|
69
|
-
|
|
70
|
-
let artifactPath = target;
|
|
97
|
+
let artifactPath = path.resolve(target);
|
|
71
98
|
if (!fs.existsSync(artifactPath)) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
99
|
+
if (fs.existsSync(path.join(home, 'artifacts', target))) {
|
|
100
|
+
artifactPath = path.join(home, 'artifacts', target);
|
|
101
|
+
} else {
|
|
102
|
+
console.log(`${C.red}✖ Artifact not found:${C.reset} ${target}`);
|
|
103
|
+
process.exit(1);
|
|
75
104
|
}
|
|
76
105
|
}
|
|
77
106
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
107
|
+
const content = fs.readFileSync(artifactPath, 'utf8');
|
|
108
|
+
const artifact = JSON.parse(content);
|
|
109
|
+
|
|
110
|
+
// Init Witness
|
|
111
|
+
logWitness(home, 'start', { artifact: artifactPath, dryRun: dryRun });
|
|
83
112
|
|
|
113
|
+
// Header
|
|
84
114
|
console.log(`\n${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}`);
|
|
85
115
|
console.log(`${C.cyan}${C.bold}JOS RUN${C.reset} // ${C.gray}JOSFOXAI MAGIC Runtime${C.reset}`);
|
|
86
116
|
console.log(`${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`);
|
|
87
117
|
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
// Mode indicator
|
|
119
|
+
if (dryRun) {
|
|
120
|
+
console.log(`${C.yellow}${C.bold}⚡ DRY RUN MODE${C.reset} ${C.gray}— No commands will be executed${C.reset}\n`);
|
|
121
|
+
}
|
|
92
122
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
123
|
+
// Artifact info
|
|
124
|
+
console.log(`${C.cyan}📦 Artifact:${C.reset} ${artifact.meta?.name || path.basename(artifactPath)}`);
|
|
125
|
+
console.log(`${C.cyan}📄 File:${C.reset} ${path.basename(artifactPath)}`);
|
|
126
|
+
console.log(`${C.cyan}🆔 ID:${C.reset} ${artifact.id_jos || 'N/A'}`);
|
|
127
|
+
|
|
128
|
+
// Integrity verification
|
|
129
|
+
if (!insecure) {
|
|
130
|
+
const integrity = verifyIntegrity(artifactPath, content, artifact);
|
|
131
|
+
if (!integrity.valid) {
|
|
132
|
+
console.log(`${C.cyan}🔐 Integrity:${C.reset} ${C.red}FAILED - ${integrity.error}${C.reset}`);
|
|
133
|
+
logWitness(home, 'integrity_failed', { error: integrity.error });
|
|
134
|
+
process.exit(1);
|
|
135
|
+
} else if (integrity.verified) {
|
|
136
|
+
console.log(`${C.cyan}🔐 Integrity:${C.reset} ${C.green}✓ Verified${C.reset} ${C.gray}(${integrity.hash.substring(0, 16)}...)${C.reset}`);
|
|
137
|
+
logWitness(home, 'integrity_verified', { hash: integrity.hash });
|
|
138
|
+
} else {
|
|
139
|
+
console.log(`${C.cyan}🔐 Integrity:${C.reset} ${C.yellow}⚠ ${integrity.warning}${C.reset}`);
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
console.log(`${C.cyan}🔐 Integrity:${C.reset} ${C.yellow}⚠ Skipped (--insecure)${C.reset}`);
|
|
98
143
|
}
|
|
99
144
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
console.log(`${C.
|
|
103
|
-
console.log(`${C.white}🔐 Integrity:${C.reset} ${C.green}${integrity.substring(0, 16)}...${C.reset}`);
|
|
145
|
+
// MAGIC validation
|
|
146
|
+
const hasMagic = artifact.intention && artifact.guardrails && artifact.capabilities;
|
|
147
|
+
console.log(`${C.cyan}✨ MAGIC:${C.reset} ${hasMagic ? C.green + '✓ Valid' : C.yellow + '⚠ Partial'}${C.reset}`);
|
|
104
148
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
console.log(`\n${C.red}✖ MAGIC Validation Failed:${C.reset}`);
|
|
109
|
-
validation.errors.forEach(e => console.log(` ${C.red}•${C.reset} ${e}`));
|
|
110
|
-
process.exit(1);
|
|
149
|
+
// Intention
|
|
150
|
+
if (artifact.intention?.objective) {
|
|
151
|
+
console.log(`${C.cyan}🎯 Intention:${C.reset} ${artifact.intention.objective}`);
|
|
111
152
|
}
|
|
112
|
-
console.log(`${C.white}✓ MAGIC:${C.reset} ${C.green}Valid${C.reset}`);
|
|
113
153
|
|
|
114
|
-
//
|
|
115
|
-
const intention = artifact.intention?.objective || artifact.meta?.intention || 'No intention defined';
|
|
116
|
-
console.log(`${C.white}🎯 Intention:${C.reset} ${intention}`);
|
|
117
|
-
|
|
118
|
-
// Check guardrails
|
|
154
|
+
// Guardrails
|
|
119
155
|
if (artifact.guardrails?.avoid?.length > 0) {
|
|
120
|
-
console.log(`${C.
|
|
156
|
+
console.log(`${C.cyan}🛡️ Guardrails:${C.reset} ${artifact.guardrails.avoid.join(', ')}`);
|
|
121
157
|
}
|
|
122
158
|
|
|
123
|
-
//
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
console.log(
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (dryRun) {
|
|
139
|
-
console.log(`\n${C.yellow}⚡ Dry run - no execution${C.reset}`);
|
|
140
|
-
return;
|
|
159
|
+
// Flow selection
|
|
160
|
+
const flowName = args.includes('--flow') ? args[args.indexOf('--flow') + 1] : 'main';
|
|
161
|
+
const flow = artifact.orchestration?.flows?.[flowName] ||
|
|
162
|
+
artifact.orchestration?.pipelines?.[flowName];
|
|
163
|
+
|
|
164
|
+
if (!flow) {
|
|
165
|
+
console.log(`\n${C.red}✖ Flow '${flowName}' not found.${C.reset}`);
|
|
166
|
+
const available = [
|
|
167
|
+
...Object.keys(artifact.orchestration?.flows || {}),
|
|
168
|
+
...Object.keys(artifact.orchestration?.pipelines || {})
|
|
169
|
+
];
|
|
170
|
+
if (available.length > 0) {
|
|
171
|
+
console.log(`${C.gray}Available flows: ${available.join(', ')}${C.reset}`);
|
|
141
172
|
}
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
142
175
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
console.log(
|
|
158
|
-
|
|
176
|
+
// Execution plan
|
|
177
|
+
console.log(`\n${C.cyan}▶ Flow:${C.reset} ${C.bold}${flowName}${C.reset}`);
|
|
178
|
+
if (flow.description) {
|
|
179
|
+
console.log(`${C.gray} ${flow.description}${C.reset}`);
|
|
180
|
+
}
|
|
181
|
+
console.log(`${C.gray} Steps: ${flow.steps?.join(' → ') || 'none'}${C.reset}\n`);
|
|
182
|
+
|
|
183
|
+
// Execute steps
|
|
184
|
+
let stepIndex = 0;
|
|
185
|
+
for (const stepName of flow.steps || []) {
|
|
186
|
+
stepIndex++;
|
|
187
|
+
const def = artifact.orchestration.definitions?.[stepName];
|
|
188
|
+
|
|
189
|
+
if (!def) {
|
|
190
|
+
console.log(`${C.red} [${stepIndex}] ✖ ${stepName}: Definition not found${C.reset}`);
|
|
191
|
+
logWitness(home, 'step_error', { step: stepName, error: 'definition not found' });
|
|
192
|
+
continue;
|
|
159
193
|
}
|
|
160
194
|
|
|
161
|
-
|
|
162
|
-
for (const name of taskNames) {
|
|
163
|
-
await executeTask(name, artifact.tasks[name]);
|
|
164
|
-
}
|
|
195
|
+
logWitness(home, 'step_start', { step: stepName, type: def.type, index: stepIndex });
|
|
165
196
|
|
|
166
|
-
|
|
167
|
-
// Simple flow mode
|
|
168
|
-
console.log(` ${C.dim}Mode: Flow${C.reset}`);
|
|
169
|
-
artifact.flow.steps.forEach((step, i) => {
|
|
170
|
-
console.log(` ${C.cyan}${i + 1}.${C.reset} ${step}`);
|
|
171
|
-
});
|
|
197
|
+
const typeIcon = def.type === 'shell' ? '⚡' : def.type === 'jos' ? '📦' : '●';
|
|
172
198
|
|
|
173
199
|
if (dryRun) {
|
|
174
|
-
console.log(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
200
|
+
console.log(`${C.yellow} [${stepIndex}] ${typeIcon} ${stepName}${C.reset}`);
|
|
201
|
+
if (def.description) console.log(`${C.gray} ${def.description}${C.reset}`);
|
|
202
|
+
if (def.type === 'shell') {
|
|
203
|
+
const cmdPreview = def.command.length > 60 ? def.command.substring(0, 60) + '...' : def.command;
|
|
204
|
+
console.log(`${C.dim} $ ${cmdPreview}${C.reset}`);
|
|
205
|
+
} else if (def.type === 'jos') {
|
|
206
|
+
console.log(`${C.dim} → ${def.artifact}${C.reset}`);
|
|
207
|
+
}
|
|
208
|
+
logWitness(home, 'step_dry_run', { step: stepName });
|
|
209
|
+
continue;
|
|
181
210
|
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
console.log(`\n${C.green}✓ Execution complete${C.reset}\n`);
|
|
185
|
-
};
|
|
186
211
|
|
|
187
|
-
|
|
188
|
-
|
|
212
|
+
// Real execution
|
|
213
|
+
console.log(`${C.blue} [${stepIndex}] ${typeIcon} ${stepName}...${C.reset}`);
|
|
189
214
|
|
|
190
|
-
for (const stepRef of pipeline.steps || []) {
|
|
191
|
-
// Parse step reference (e.g., "tasks.build")
|
|
192
|
-
const [type, taskName] = stepRef.split('.');
|
|
193
|
-
|
|
194
|
-
if (type === 'tasks' && artifact.tasks[taskName]) {
|
|
195
|
-
await executeTask(taskName, artifact.tasks[taskName]);
|
|
196
|
-
} else {
|
|
197
|
-
console.log(` ${C.yellow}⚠ Unknown step: ${stepRef}${C.reset}`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async function executeTask(name, task) {
|
|
203
|
-
console.log(`\n${C.cyan}▶ Task: ${name}${C.reset}`);
|
|
204
|
-
if (task.description) console.log(` ${C.dim}${task.description}${C.reset}`);
|
|
205
|
-
|
|
206
|
-
if (task.shell && Array.isArray(task.shell)) {
|
|
207
|
-
const script = task.shell.join('\n');
|
|
208
215
|
try {
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
if (def.type === 'shell') {
|
|
217
|
+
execSync(def.command, { stdio: 'inherit', shell: '/bin/bash' });
|
|
218
|
+
console.log(`${C.green} ✓ Complete${C.reset}`);
|
|
219
|
+
} else if (def.type === 'jos') {
|
|
220
|
+
console.log(`${C.purple} Running sub-artifact: ${def.artifact}${C.reset}`);
|
|
221
|
+
const subPath = path.resolve(path.dirname(artifactPath), def.artifact);
|
|
222
|
+
const binJos = process.argv[1];
|
|
223
|
+
execSync(`${process.execPath} "${binJos}" run "${subPath}" ${insecure ? '--insecure' : ''} --flow main`, {
|
|
224
|
+
stdio: 'inherit'
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
logWitness(home, 'step_complete', { step: stepName, success: true });
|
|
211
228
|
} catch (e) {
|
|
212
|
-
console.log(
|
|
213
|
-
|
|
229
|
+
console.log(`${C.red} ✖ Failed: ${e.message}${C.reset}`);
|
|
230
|
+
logWitness(home, 'step_failed', { step: stepName, error: e.message });
|
|
231
|
+
logWitness(home, 'run_failed', { lastStep: stepName });
|
|
232
|
+
process.exit(1);
|
|
214
233
|
}
|
|
215
234
|
}
|
|
216
|
-
}
|
|
217
235
|
|
|
218
|
-
|
|
219
|
-
console.log(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
} catch (e) {
|
|
223
|
-
console.log(` ${C.red}✖ Step failed${C.reset}`);
|
|
236
|
+
// Success
|
|
237
|
+
console.log(`\n${C.green}${C.bold}✓ ${dryRun ? 'Dry run complete' : 'Execution complete'}${C.reset}`);
|
|
238
|
+
if (artifact.intention?.success_criteria) {
|
|
239
|
+
console.log(`${C.gray} Success criteria: ${artifact.intention.success_criteria}${C.reset}`);
|
|
224
240
|
}
|
|
225
|
-
|
|
241
|
+
console.log('');
|
|
242
|
+
|
|
243
|
+
logWitness(home, 'success', { dryRun: dryRun, stepsExecuted: stepIndex });
|
|
244
|
+
};
|