@josfox/jos 4.0.4 → 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.
@@ -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
+ };
@@ -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', magenta: '\x1b[38;5;198m', cyan: '\x1b[38;5;51m',
15
- green: '\x1b[38;5;78m', red: '\x1b[38;5;196m', gray: '\x1b[38;5;245m',
16
- white: '\x1b[38;5;255m', yellow: '\x1b[38;5;220m'
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
- // Validate JOSFOXAI MAGIC contract (flexible for different schemas)
25
- function validateMagic(artifact) {
26
- const errors = [];
27
- const warnings = [];
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
- // MAGIC components (Intention) - flexible detection
30
- const hasMeta = artifact.meta || artifact._josfox || artifact.jos_schema;
31
- const hasIntention = artifact.intention || artifact.meta?.intention ||
32
- artifact.description || artifact._josfox?.description;
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
- if (!hasMeta) warnings.push('Recommended: meta or _josfox');
35
- if (!hasIntention) warnings.push('Recommended: intention or description');
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
- // JOSFOXAI components (Execution) - at least one required
38
- const hasExecution = artifact.flow || artifact.tasks || artifact.pipelines ||
39
- artifact.jos || artifact.shell;
40
- if (!hasExecution) errors.push('Missing: execution (flow/tasks/pipelines/shell)');
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: errors.length === 0, errors, warnings };
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 showHelp = args.includes('--help') || args.includes('-h');
75
+ const insecure = args.includes('--insecure');
49
76
 
50
- if (showHelp || !target) {
77
+ if (args.includes('--help') || !target) {
51
78
  console.log(`
52
- ${C.cyan}${C.bold}JOS RUN${C.reset} - Execute .jos artifacts
79
+ ${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
80
+ ${C.bold}JOS RUN${C.reset} // Execute .jos artifacts
81
+ ${C.purple}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}
53
82
 
54
- ${C.white}Usage:${C.reset} jos run <file.jos> [options]
83
+ ${C.bold}Usage:${C.reset}
84
+ jos run <file.jos> [options]
55
85
 
56
- ${C.white}Options:${C.reset}
57
- --dry-run Validate and show plan without executing
58
- --task <name> Run specific task only
59
- --help, -h Show this help
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.white}Examples:${C.reset}
62
- jos run hello.jos
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
- // Resolve path
70
- let artifactPath = target;
97
+ let artifactPath = path.resolve(target);
71
98
  if (!fs.existsSync(artifactPath)) {
72
- artifactPath = path.join(home, 'artifacts', target);
73
- if (!fs.existsSync(artifactPath)) {
74
- artifactPath = path.join(process.cwd(), target);
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
- if (!fs.existsSync(artifactPath)) {
79
- console.log(`${C.red}✖ Artifact not found:${C.reset} ${target}`);
80
- console.log(`${C.dim} Searched: ./, ~/.jos/artifacts/${C.reset}`);
81
- process.exit(1);
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
- // Load artifact
89
- const content = fs.readFileSync(artifactPath, 'utf8');
90
- const integrity = computeIntegrity(content);
91
- let artifact;
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
- try {
94
- artifact = JSON.parse(content);
95
- } catch (e) {
96
- console.log(`${C.red} Invalid JSON:${C.reset} ${e.message}`);
97
- process.exit(1);
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
- // Display metadata
101
- console.log(`${C.white}📦 Artifact:${C.reset} ${artifact.meta?.name || artifact.name || path.basename(artifactPath)}`);
102
- console.log(`${C.white}📄 File:${C.reset} ${artifactPath}`);
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
- // Validate MAGIC
106
- const validation = validateMagic(artifact);
107
- if (!validation.valid) {
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
- // Show intention
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.white}🛡️ Guardrails:${C.reset} ${artifact.guardrails.avoid.join(', ')}`);
156
+ console.log(`${C.cyan}🛡️ Guardrails:${C.reset} ${artifact.guardrails.avoid.join(', ')}`);
121
157
  }
122
158
 
123
- // Determine execution mode
124
- const taskName = args.find((a, i) => args[i - 1] === '--task');
125
-
126
- console.log(`\n${C.cyan}▶ Execution Plan:${C.reset}`);
127
-
128
- // Execute based on artifact type
129
- if (artifact.pipelines && !taskName) {
130
- // Pipeline mode
131
- const pipelineNames = Object.keys(artifact.pipelines);
132
- console.log(` ${C.dim}Mode: Pipeline${C.reset}`);
133
- pipelineNames.forEach(name => {
134
- const pipeline = artifact.pipelines[name];
135
- console.log(` ${C.cyan}→${C.reset} ${name}: ${pipeline.steps?.length || 0} steps`);
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
- // Execute first pipeline
144
- const firstPipeline = pipelineNames[0];
145
- await executePipeline(artifact, firstPipeline, artifact.pipelines[firstPipeline]);
146
-
147
- } else if (artifact.tasks) {
148
- // Task mode
149
- const taskNames = taskName ? [taskName] : Object.keys(artifact.tasks);
150
- console.log(` ${C.dim}Mode: Tasks${C.reset}`);
151
- taskNames.forEach(name => {
152
- const task = artifact.tasks[name];
153
- console.log(` ${C.cyan}→${C.reset} ${name}: ${task.description || ''}`);
154
- });
155
-
156
- if (dryRun) {
157
- console.log(`\n${C.yellow} Dry run - no execution${C.reset}`);
158
- return;
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
- // Execute tasks
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
- } else if (artifact.flow?.steps) {
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(`\n${C.yellow} Dry run - no execution${C.reset}`);
175
- return;
176
- }
177
-
178
- // Execute steps
179
- for (const step of artifact.flow.steps) {
180
- executeStep(step);
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
- async function executePipeline(artifact, name, pipeline) {
188
- console.log(`\n${C.purple} Running pipeline: ${name}${C.reset}\n`);
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
- execSync(script, { stdio: 'inherit', shell: '/bin/bash' });
210
- console.log(` ${C.green}✓ Task complete${C.reset}`);
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(` ${C.red}✖ Task failed: ${e.message}${C.reset}`);
213
- throw e;
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
- function executeStep(step) {
219
- console.log(` ${C.cyan}▶${C.reset} ${step}`);
220
- try {
221
- execSync(step, { stdio: 'inherit', shell: true });
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
+ };