arkaos 4.32.0 → 4.34.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 (92) hide show
  1. package/README.md +1 -1
  2. package/THE-ARKAOS-GUIDE.md +1 -1
  3. package/VERSION +1 -1
  4. package/arka/SKILL.md +1 -1
  5. package/arka/skills/forge/references/workflows.md +10 -0
  6. package/config/claude-agents/architect.md +1 -1
  7. package/config/claude-agents/backend-dev.md +1 -1
  8. package/config/claude-agents/devops-eng.md +1 -1
  9. package/config/claude-agents/frontend-dev.md +1 -1
  10. package/config/claude-agents/ops-lead.md +1 -1
  11. package/config/claude-agents/pm-director.md +1 -1
  12. package/config/claude-agents/strategy-director.md +1 -1
  13. package/config/hooks/session-start.sh +1 -1
  14. package/config/skills-curated.yaml +2 -2
  15. package/config/skills-provenance.yaml +7 -0
  16. package/config/statusline.sh +6 -1
  17. package/core/hooks/session_start.py +69 -28
  18. package/departments/dev/agents/architect.yaml +1 -0
  19. package/departments/dev/agents/backend-dev.yaml +1 -0
  20. package/departments/dev/agents/devops-eng.yaml +1 -0
  21. package/departments/dev/agents/frontend-dev.yaml +1 -0
  22. package/departments/dev/agents/tech-lead.yaml +1 -0
  23. package/departments/dev/skills/diagram/SKILL.md +175 -0
  24. package/departments/dev/skills/diagram/examples/archify-repo-grid.architecture.json +57 -0
  25. package/departments/dev/skills/diagram/vendor/LICENSE +22 -0
  26. package/departments/dev/skills/diagram/vendor/SKILL.md +367 -0
  27. package/departments/dev/skills/diagram/vendor/assets/template.html +11470 -0
  28. package/departments/dev/skills/diagram/vendor/bin/archify.mjs +600 -0
  29. package/departments/dev/skills/diagram/vendor/bin/open-artifact.mjs +67 -0
  30. package/departments/dev/skills/diagram/vendor/examples/agent-run.lifecycle.json +71 -0
  31. package/departments/dev/skills/diagram/vendor/examples/agent-tool-call.workflow.json +111 -0
  32. package/departments/dev/skills/diagram/vendor/examples/async-job-roundtrip.sequence.json +62 -0
  33. package/departments/dev/skills/diagram/vendor/examples/cache-miss-request.sequence.json +82 -0
  34. package/departments/dev/skills/diagram/vendor/examples/deployment-release.lifecycle.json +50 -0
  35. package/departments/dev/skills/diagram/vendor/examples/event-stream.dataflow.json +58 -0
  36. package/departments/dev/skills/diagram/vendor/examples/incident-response.workflow.json +65 -0
  37. package/departments/dev/skills/diagram/vendor/examples/product-analytics.dataflow.json +77 -0
  38. package/departments/dev/skills/diagram/vendor/examples/production-deployment.architecture.json +70 -0
  39. package/departments/dev/skills/diagram/vendor/examples/release-delivery.workflow.json +63 -0
  40. package/departments/dev/skills/diagram/vendor/examples/web-app.architecture.json +47 -0
  41. package/departments/dev/skills/diagram/vendor/package-lock.json +80 -0
  42. package/departments/dev/skills/diagram/vendor/package.json +28 -0
  43. package/departments/dev/skills/diagram/vendor/recipes/scenarios.mjs +350 -0
  44. package/departments/dev/skills/diagram/vendor/renderers/architecture/grid.mjs +62 -0
  45. package/departments/dev/skills/diagram/vendor/renderers/architecture/render-architecture.mjs +490 -0
  46. package/departments/dev/skills/diagram/vendor/renderers/dataflow/README.md +93 -0
  47. package/departments/dev/skills/diagram/vendor/renderers/dataflow/render-dataflow.mjs +396 -0
  48. package/departments/dev/skills/diagram/vendor/renderers/lifecycle/README.md +103 -0
  49. package/departments/dev/skills/diagram/vendor/renderers/lifecycle/render-lifecycle.mjs +482 -0
  50. package/departments/dev/skills/diagram/vendor/renderers/sequence/README.md +96 -0
  51. package/departments/dev/skills/diagram/vendor/renderers/sequence/render-sequence.mjs +357 -0
  52. package/departments/dev/skills/diagram/vendor/renderers/shared/cli.mjs +176 -0
  53. package/departments/dev/skills/diagram/vendor/renderers/shared/generated-validators.mjs +13 -0
  54. package/departments/dev/skills/diagram/vendor/renderers/shared/geometry.mjs +992 -0
  55. package/departments/dev/skills/diagram/vendor/renderers/shared/layout-report.mjs +40 -0
  56. package/departments/dev/skills/diagram/vendor/renderers/shared/utils.mjs +156 -0
  57. package/departments/dev/skills/diagram/vendor/renderers/shared/validator.mjs +40 -0
  58. package/departments/dev/skills/diagram/vendor/renderers/workflow/README.md +114 -0
  59. package/departments/dev/skills/diagram/vendor/renderers/workflow/render-workflow.mjs +598 -0
  60. package/departments/dev/skills/diagram/vendor/schemas/README.md +93 -0
  61. package/departments/dev/skills/diagram/vendor/schemas/architecture.schema.json +125 -0
  62. package/departments/dev/skills/diagram/vendor/schemas/common.schema.json +62 -0
  63. package/departments/dev/skills/diagram/vendor/schemas/dataflow.schema.json +240 -0
  64. package/departments/dev/skills/diagram/vendor/schemas/lifecycle.schema.json +260 -0
  65. package/departments/dev/skills/diagram/vendor/schemas/sequence.schema.json +205 -0
  66. package/departments/dev/skills/diagram/vendor/schemas/workflow.schema.json +351 -0
  67. package/departments/dev/skills/diagram/vendor/scripts/check-render-output.mjs +774 -0
  68. package/departments/dev/skills/diagram/vendor/scripts/generate-validators.mjs +66 -0
  69. package/departments/dev/skills/diagram/vendor/scripts/render-examples.mjs +26 -0
  70. package/departments/dev/skills/spec/SKILL.md +19 -0
  71. package/departments/ops/agents/ops-lead.yaml +1 -0
  72. package/departments/pm/agents/pm-director.yaml +1 -0
  73. package/departments/strategy/agents/strategy-director.yaml +1 -0
  74. package/harness/codex/AGENTS.md +1 -1
  75. package/harness/copilot/copilot-instructions.md +1 -1
  76. package/harness/cursor/rules/arkaos.mdc +2 -2
  77. package/harness/gemini/GEMINI.md +1 -1
  78. package/harness/opencode/AGENTS.md +1 -1
  79. package/harness/zed/.rules +1 -1
  80. package/installer/autoupdate.js +249 -0
  81. package/installer/cli.js +7 -0
  82. package/installer/core-snapshot.js +5 -1
  83. package/installer/doctor.js +11 -0
  84. package/installer/index.js +11 -0
  85. package/installer/update.js +11 -0
  86. package/knowledge/agents-registry-v2.json +9 -1
  87. package/knowledge/skills-manifest.json +14 -1
  88. package/package.json +3 -2
  89. package/pyproject.toml +1 -1
  90. package/scripts/auto-update.sh +188 -0
  91. package/scripts/skill_validator.py +10 -2
  92. package/scripts/tools/docs_stats.py +8 -2
@@ -0,0 +1,600 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from 'node:child_process';
4
+ import { createHash } from 'node:crypto';
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { fileURLToPath, pathToFileURL } from 'node:url';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const skillRoot = path.resolve(__dirname, '..');
12
+
13
+ const TYPES = new Set(['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']);
14
+
15
+ function usage() {
16
+ return `Usage:
17
+ archify render <type> <input.json> [output.html] [--quality standard|showcase]
18
+ archify deliver <type> <input.json> [output.html] [--json] [--open] [--quality standard|showcase]
19
+ archify validate <type> <input.json> [--json] [--layout-json] [--quality standard|showcase]
20
+ archify inspect <type> <input.json>
21
+ archify check <output.html>
22
+ archify guide [scenario or question] [--json] [--lang en|zh]
23
+ archify examples
24
+ archify doctor
25
+ archify demo [output-directory]
26
+
27
+ Types:
28
+ architecture, workflow, sequence, dataflow, lifecycle
29
+ `;
30
+ }
31
+
32
+ function fail(message, code = 2) {
33
+ console.error(message);
34
+ process.exit(code);
35
+ }
36
+
37
+ function rendererPath(type) {
38
+ if (!TYPES.has(type)) {
39
+ fail(`Unknown diagram type "${type}". Expected one of: ${[...TYPES].join(', ')}`);
40
+ }
41
+ return path.join(skillRoot, 'renderers', type, `render-${type}.mjs`);
42
+ }
43
+
44
+ function runNode(args, options = {}) {
45
+ return spawnSync(process.execPath, args, {
46
+ cwd: options.cwd || process.cwd(),
47
+ encoding: 'utf8',
48
+ stdio: options.stdio || 'inherit',
49
+ env: options.env ? { ...process.env, ...options.env } : process.env,
50
+ });
51
+ }
52
+
53
+ function extractQualityArgs(args) {
54
+ const rest = [];
55
+ let quality;
56
+ for (let index = 0; index < args.length; index += 1) {
57
+ const arg = args[index];
58
+ if (arg === '--quality') {
59
+ quality = args[index + 1];
60
+ index += 1;
61
+ continue;
62
+ }
63
+ if (arg.startsWith('--quality=')) {
64
+ quality = arg.slice('--quality='.length);
65
+ continue;
66
+ }
67
+ rest.push(arg);
68
+ }
69
+ if (quality !== undefined && !['standard', 'showcase'].includes(quality)) {
70
+ fail(`Unknown quality profile "${quality}". Expected standard or showcase.`);
71
+ }
72
+ return { rest, quality };
73
+ }
74
+
75
+ function exitFrom(result) {
76
+ if (result.error) fail(result.error.message, 1);
77
+ process.exit(result.status ?? 1);
78
+ }
79
+
80
+ function commandRender(args) {
81
+ const { rest, quality } = extractQualityArgs(args);
82
+ const [type, input, output] = rest;
83
+ if (!type || !input) fail(usage());
84
+ const result = runNode([rendererPath(type), input, ...(output ? [output] : [])], {
85
+ env: quality ? { ARCHIFY_QUALITY_PROFILE: quality } : undefined,
86
+ });
87
+ if (result.status !== 0) exitFrom(result);
88
+ }
89
+
90
+ function reportDeliveryFailure({ json, stage, type, input, output, error, status = 1, checker }) {
91
+ const receipt = {
92
+ schemaVersion: 1,
93
+ ok: false,
94
+ command: 'deliver',
95
+ stage,
96
+ type,
97
+ input,
98
+ output,
99
+ error,
100
+ ...(checker ? { checker } : {}),
101
+ };
102
+ if (json) console.log(JSON.stringify(receipt, null, 2));
103
+ else console.error(error);
104
+ process.exitCode = status;
105
+ }
106
+
107
+ async function commandDeliver(args) {
108
+ const qualityArgs = extractQualityArgs(args);
109
+ const json = qualityArgs.rest.includes('--json');
110
+ const open = qualityArgs.rest.includes('--open');
111
+ const knownOptions = new Set(['--json', '--open']);
112
+ const unknown = qualityArgs.rest.filter((arg) => arg.startsWith('--') && !knownOptions.has(arg));
113
+ if (unknown.length) fail(`Unknown deliver option "${unknown[0]}".`);
114
+ const positional = qualityArgs.rest.filter((arg) => !knownOptions.has(arg));
115
+ const [type, input, requestedOutput] = positional;
116
+ if (!type || !input || positional.length > 3) fail(usage());
117
+
118
+ const renderer = rendererPath(type);
119
+ const inputPath = path.resolve(input);
120
+ let diagram;
121
+ try {
122
+ diagram = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
123
+ } catch (error) {
124
+ reportDeliveryFailure({
125
+ json,
126
+ stage: 'input',
127
+ type,
128
+ input: inputPath,
129
+ output: path.resolve(requestedOutput || `${type}.html`),
130
+ error: `Could not read delivery input "${inputPath}": ${error.message}`,
131
+ });
132
+ return;
133
+ }
134
+
135
+ const authoredOutput = typeof diagram?.meta?.output === 'string' && diagram.meta.output
136
+ ? diagram.meta.output
137
+ : `${type}.html`;
138
+ const outputPath = path.resolve(requestedOutput || authoredOutput);
139
+ const outputDirectory = path.dirname(outputPath);
140
+ try {
141
+ fs.mkdirSync(outputDirectory, { recursive: true });
142
+ } catch (error) {
143
+ reportDeliveryFailure({
144
+ json,
145
+ stage: 'prepare',
146
+ type,
147
+ input: inputPath,
148
+ output: outputPath,
149
+ error: `Could not create delivery directory "${outputDirectory}": ${error.message}`,
150
+ });
151
+ return;
152
+ }
153
+
154
+ // Keep the candidate beside the target so the final rename is one
155
+ // same-filesystem commit. A render or artifact-check failure never touches
156
+ // an existing trusted output.
157
+ let stagingDirectory;
158
+ try {
159
+ stagingDirectory = fs.mkdtempSync(path.join(outputDirectory, '.archify-delivery-'));
160
+ } catch (error) {
161
+ reportDeliveryFailure({
162
+ json,
163
+ stage: 'prepare',
164
+ type,
165
+ input: inputPath,
166
+ output: outputPath,
167
+ error: `Could not create a delivery candidate beside "${outputPath}": ${error.message}`,
168
+ });
169
+ return;
170
+ }
171
+ const candidatePath = path.join(stagingDirectory, path.basename(outputPath));
172
+
173
+ try {
174
+ const render = runNode([renderer, inputPath, candidatePath], {
175
+ stdio: 'pipe',
176
+ env: qualityArgs.quality ? { ARCHIFY_QUALITY_PROFILE: qualityArgs.quality } : undefined,
177
+ });
178
+ if (render.status !== 0) {
179
+ if (render.stderr) process.stderr.write(render.stderr);
180
+ if (render.stdout) process.stderr.write(render.stdout);
181
+ reportDeliveryFailure({
182
+ json,
183
+ stage: 'render',
184
+ type,
185
+ input: inputPath,
186
+ output: outputPath,
187
+ error: (render.stderr || render.stdout || 'Renderer failed without a diagnostic.').trim(),
188
+ status: render.status ?? 1,
189
+ });
190
+ return;
191
+ }
192
+
193
+ const check = runNode([path.join(skillRoot, 'scripts/check-render-output.mjs'), candidatePath], {
194
+ stdio: 'pipe',
195
+ });
196
+ if (check.status !== 0) {
197
+ if (check.stderr) process.stderr.write(check.stderr);
198
+ let checker;
199
+ try {
200
+ checker = JSON.parse(check.stdout);
201
+ checker.file = outputPath;
202
+ } catch {
203
+ checker = { ok: false, file: outputPath, diagnostic: check.stdout.trim() };
204
+ }
205
+ reportDeliveryFailure({
206
+ json,
207
+ stage: 'check',
208
+ type,
209
+ input: inputPath,
210
+ output: outputPath,
211
+ error: 'Final artifact check failed; the previous artifact was preserved.',
212
+ status: check.status ?? 1,
213
+ checker,
214
+ });
215
+ return;
216
+ }
217
+
218
+ let result;
219
+ try {
220
+ result = JSON.parse(check.stdout);
221
+ } catch (error) {
222
+ reportDeliveryFailure({
223
+ json,
224
+ stage: 'receipt',
225
+ type,
226
+ input: inputPath,
227
+ output: outputPath,
228
+ error: `Could not parse the successful artifact-check receipt: ${error.message}`,
229
+ });
230
+ return;
231
+ }
232
+ let artifact;
233
+ try {
234
+ artifact = fs.readFileSync(candidatePath);
235
+ } catch (error) {
236
+ reportDeliveryFailure({
237
+ json,
238
+ stage: 'receipt',
239
+ type,
240
+ input: inputPath,
241
+ output: outputPath,
242
+ error: `Could not read the verified delivery candidate: ${error.message}`,
243
+ });
244
+ return;
245
+ }
246
+ const receipt = {
247
+ schemaVersion: 1,
248
+ ok: true,
249
+ command: 'deliver',
250
+ type,
251
+ input: inputPath,
252
+ output: outputPath,
253
+ artifact: {
254
+ sha256: createHash('sha256').update(artifact).digest('hex'),
255
+ bytes: artifact.byteLength,
256
+ },
257
+ validation: {
258
+ checksPassed: result.checks.filter((checkItem) => checkItem.ok).length,
259
+ checkCount: result.checks.length,
260
+ compositionProfile: result.composition.profile,
261
+ compositionStatus: result.composition.status,
262
+ errors: result.composition.summary.errors,
263
+ warnings: result.composition.summary.warnings,
264
+ },
265
+ };
266
+
267
+ try {
268
+ fs.renameSync(candidatePath, outputPath);
269
+ } catch (error) {
270
+ reportDeliveryFailure({
271
+ json,
272
+ stage: 'commit',
273
+ type,
274
+ input: inputPath,
275
+ output: outputPath,
276
+ error: `Could not commit verified delivery "${outputPath}": ${error.message}`,
277
+ });
278
+ return;
279
+ }
280
+
281
+ if (open) {
282
+ try {
283
+ const { openArtifact } = await import('./open-artifact.mjs');
284
+ receipt.open = openArtifact(outputPath);
285
+ } catch {
286
+ receipt.open = {
287
+ requested: true,
288
+ status: 'unsupported',
289
+ target: outputPath,
290
+ method: null,
291
+ };
292
+ }
293
+ if (receipt.open.status !== 'opened') {
294
+ console.error(`Could not open the verified artifact (${receipt.open.status}). Open it manually: ${outputPath}`);
295
+ }
296
+ }
297
+
298
+ if (json) {
299
+ console.log(JSON.stringify(receipt, null, 2));
300
+ } else {
301
+ console.log(`delivered ${type} ${outputPath}`);
302
+ console.log(`${receipt.validation.checksPassed}/${receipt.validation.checkCount} artifact checks; composition ${receipt.validation.compositionProfile}: ${receipt.validation.compositionStatus}; sha256 ${receipt.artifact.sha256.slice(0, 12)}`);
303
+ if (receipt.open?.status === 'opened') console.log(`opened ${outputPath}`);
304
+ }
305
+ } finally {
306
+ try {
307
+ fs.rmSync(stagingDirectory, { recursive: true, force: true });
308
+ } catch (error) {
309
+ console.error(`Warning: could not remove delivery staging directory "${stagingDirectory}": ${error.message}`);
310
+ }
311
+ }
312
+ }
313
+
314
+ function commandCheck(args) {
315
+ const [html] = args;
316
+ if (!html) fail(usage());
317
+ const result = runNode([path.join(skillRoot, 'scripts/check-render-output.mjs'), html]);
318
+ if (result.status !== 0) exitFrom(result);
319
+ }
320
+
321
+ function commandExamples() {
322
+ const result = runNode([path.join(skillRoot, 'scripts/render-examples.mjs')], { cwd: skillRoot });
323
+ if (result.status !== 0) exitFrom(result);
324
+ }
325
+
326
+ async function commandDoctor() {
327
+ const checks = [];
328
+ const nodeMajor = Number.parseInt(process.versions.node.split('.')[0], 10);
329
+ checks.push({
330
+ label: `Node.js v${process.versions.node} (requires >=18)`,
331
+ ok: nodeMajor >= 18,
332
+ missing: 0,
333
+ failureLabel: 'unsupported',
334
+ });
335
+
336
+ const template = path.join(skillRoot, 'assets/template.html');
337
+ checks.push({
338
+ label: 'Core template',
339
+ ok: fs.existsSync(template),
340
+ missing: fs.existsSync(template) ? 0 : 1,
341
+ });
342
+
343
+ const examplesRenderer = path.join(skillRoot, 'scripts/render-examples.mjs');
344
+ checks.push({
345
+ label: 'Example renderer',
346
+ ok: fs.existsSync(examplesRenderer),
347
+ missing: fs.existsSync(examplesRenderer) ? 0 : 1,
348
+ });
349
+
350
+ const scenarioGuide = path.join(skillRoot, 'recipes/scenarios.mjs');
351
+ checks.push({
352
+ label: 'Scenario recipe guide',
353
+ ok: fs.existsSync(scenarioGuide),
354
+ missing: fs.existsSync(scenarioGuide) ? 0 : 1,
355
+ });
356
+
357
+ const validators = path.join(skillRoot, 'renderers/shared/generated-validators.mjs');
358
+ const validatorsExist = fs.existsSync(validators);
359
+ let validatorsValid = false;
360
+ if (validatorsExist) {
361
+ try {
362
+ const module = await import(`${pathToFileURL(validators).href}?doctor=${Date.now()}`);
363
+ validatorsValid = [...TYPES].every((type) => typeof module[type] === 'function');
364
+ } catch {
365
+ validatorsValid = false;
366
+ }
367
+ }
368
+ checks.push({
369
+ label: 'Standalone schema validators',
370
+ ok: validatorsValid,
371
+ missing: validatorsExist ? 0 : 1,
372
+ invalid: validatorsExist && !validatorsValid ? 1 : 0,
373
+ failureLabel: validatorsExist ? 'invalid' : 'missing',
374
+ });
375
+
376
+ const examples = {
377
+ architecture: 'web-app.architecture.json',
378
+ workflow: 'agent-tool-call.workflow.json',
379
+ sequence: 'cache-miss-request.sequence.json',
380
+ dataflow: 'product-analytics.dataflow.json',
381
+ lifecycle: 'agent-run.lifecycle.json',
382
+ };
383
+
384
+ for (const type of TYPES) {
385
+ const required = [
386
+ path.join(skillRoot, 'renderers', type, `render-${type}.mjs`),
387
+ path.join(skillRoot, 'schemas', `${type}.schema.json`),
388
+ path.join(skillRoot, 'examples', examples[type]),
389
+ ];
390
+ const missing = required.filter((file) => !fs.existsSync(file)).length;
391
+ checks.push({
392
+ label: `${type} renderer, schema, and example`,
393
+ ok: missing === 0,
394
+ missing,
395
+ });
396
+ }
397
+
398
+ console.log('Archify doctor\n');
399
+ for (const check of checks) {
400
+ console.log(`[${check.ok ? 'ok' : (check.failureLabel || 'missing')}] ${check.label}`);
401
+ }
402
+
403
+ const nodeFailed = checks[0].ok ? 0 : 1;
404
+ const missingFiles = checks.reduce((count, check) => count + check.missing, 0);
405
+ const invalidRuntime = checks.reduce((count, check) => count + (check.invalid || 0), 0);
406
+ if (nodeFailed === 0 && missingFiles === 0 && invalidRuntime === 0) {
407
+ console.log('\nArchify is ready.');
408
+ return;
409
+ }
410
+
411
+ const problems = [];
412
+ if (nodeFailed) problems.push('Node.js 18 or newer is required');
413
+ if (missingFiles) problems.push(`${missingFiles} required file${missingFiles === 1 ? '' : 's'} missing`);
414
+ if (invalidRuntime) problems.push(`${invalidRuntime} runtime check${invalidRuntime === 1 ? '' : 's'} failed`);
415
+ console.error(`\nArchify is not ready: ${problems.join('; ')}.`);
416
+ process.exitCode = 1;
417
+ }
418
+
419
+ async function commandGuide(args) {
420
+ let lang;
421
+ let json = false;
422
+ const queryParts = [];
423
+
424
+ for (let index = 0; index < args.length; index += 1) {
425
+ const arg = args[index];
426
+ if (arg === '--json') {
427
+ json = true;
428
+ } else if (arg === '--lang') {
429
+ const value = args[index + 1];
430
+ if (value !== 'en' && value !== 'zh') fail('--lang must be "en" or "zh".');
431
+ lang = value;
432
+ index += 1;
433
+ } else if (arg.startsWith('--lang=')) {
434
+ const value = arg.slice('--lang='.length);
435
+ if (value !== 'en' && value !== 'zh') fail('--lang must be "en" or "zh".');
436
+ lang = value;
437
+ } else if (arg.startsWith('--')) {
438
+ fail(`Unknown guide option "${arg}".`);
439
+ } else {
440
+ queryParts.push(arg);
441
+ }
442
+ }
443
+
444
+ const guidePath = path.join(skillRoot, 'recipes/scenarios.mjs');
445
+ let guide;
446
+ try {
447
+ guide = await import(pathToFileURL(guidePath).href);
448
+ } catch (error) {
449
+ fail(`Could not load the scenario recipe guide: ${error.message}`, 1);
450
+ }
451
+
452
+ const query = queryParts.join(' ').trim();
453
+ if (!query) {
454
+ const selectedLang = lang || 'en';
455
+ if (json) {
456
+ console.log(JSON.stringify({
457
+ ok: true,
458
+ mode: 'list',
459
+ lang: selectedLang,
460
+ recipes: guide.listScenarioRecipes(selectedLang),
461
+ }, null, 2));
462
+ } else {
463
+ console.log(guide.formatScenarioList(selectedLang));
464
+ }
465
+ return;
466
+ }
467
+
468
+ const result = guide.recommendScenario(query, lang ? { lang } : {});
469
+ console.log(json ? JSON.stringify(result, null, 2) : guide.formatScenarioRecommendation(result));
470
+ }
471
+
472
+ function commandDemo(args) {
473
+ if (args.length > 1) fail(usage());
474
+
475
+ const outputDirectory = path.resolve(args[0] || process.cwd());
476
+ const output = path.join(outputDirectory, 'archify-demo.html');
477
+ const input = path.join(skillRoot, 'examples/web-app.architecture.json');
478
+
479
+ try {
480
+ fs.mkdirSync(outputDirectory, { recursive: true });
481
+ } catch (error) {
482
+ fail(`Could not create demo directory "${outputDirectory}": ${error.message}`, 1);
483
+ }
484
+
485
+ const result = runNode([rendererPath('architecture'), input, output]);
486
+ if (result.status !== 0) exitFrom(result);
487
+
488
+ console.log(`\nDemo ready: ${output}`);
489
+ console.log('Next: open the HTML in your browser, then render your own diagram:');
490
+ console.log(' archify render architecture <input.json> <output.html>');
491
+ }
492
+
493
+ function commandValidate(args) {
494
+ const qualityArgs = extractQualityArgs(args);
495
+ args = qualityArgs.rest;
496
+ const quality = qualityArgs.quality;
497
+ const json = args.includes('--json');
498
+ const layoutJson = args.includes('--layout-json');
499
+ const rest = args.filter((arg) => arg !== '--json' && arg !== '--layout-json');
500
+ const [type, input] = rest;
501
+ if (!type || !input) fail(usage());
502
+ const renderer = rendererPath(type);
503
+
504
+ if (layoutJson) {
505
+ if (type !== 'architecture') {
506
+ fail('--layout-json is currently supported for architecture diagrams only.');
507
+ }
508
+ const result = runNode([renderer, input, '/dev/null', '--layout-json'], {
509
+ stdio: 'pipe',
510
+ env: quality ? { ARCHIFY_QUALITY_PROFILE: quality } : undefined,
511
+ });
512
+ if (result.status !== 0) {
513
+ if (result.stderr) process.stderr.write(result.stderr);
514
+ if (result.stdout) process.stdout.write(result.stdout);
515
+ process.exit(result.status ?? 1);
516
+ }
517
+ process.stdout.write(result.stdout);
518
+ return;
519
+ }
520
+
521
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-validate-'));
522
+ const out = path.join(tmp, `${type}.html`);
523
+ let exitCode = 0;
524
+
525
+ try {
526
+ const render = runNode([renderer, input, out], {
527
+ stdio: 'pipe',
528
+ env: quality ? { ARCHIFY_QUALITY_PROFILE: quality } : undefined,
529
+ });
530
+ if (render.status !== 0) {
531
+ if (render.stderr) process.stderr.write(render.stderr);
532
+ if (render.stdout) process.stdout.write(render.stdout);
533
+ exitCode = render.status ?? 1;
534
+ } else {
535
+ const check = runNode([path.join(skillRoot, 'scripts/check-render-output.mjs'), out], { stdio: 'pipe' });
536
+ if (check.status !== 0) {
537
+ if (check.stdout) process.stdout.write(check.stdout);
538
+ if (check.stderr) process.stderr.write(check.stderr);
539
+ exitCode = check.status ?? 1;
540
+ } else {
541
+ const result = JSON.parse(check.stdout);
542
+ if (json) {
543
+ console.log(JSON.stringify({
544
+ ok: true,
545
+ type,
546
+ input: path.resolve(input),
547
+ checks: result.checks,
548
+ composition: result.composition,
549
+ }, null, 2));
550
+ } else {
551
+ console.log(`ok ${type} ${path.resolve(input)} (${result.checks.length} artifact checks; composition ${result.composition.profile}: ${result.composition.summary.errors} errors, ${result.composition.summary.warnings} warnings)`);
552
+ }
553
+ }
554
+ }
555
+ } finally {
556
+ fs.rmSync(tmp, { recursive: true, force: true });
557
+ }
558
+
559
+ if (exitCode !== 0) process.exit(exitCode);
560
+ }
561
+
562
+ const [command, ...args] = process.argv.slice(2);
563
+
564
+ switch (command) {
565
+ case undefined:
566
+ case '-h':
567
+ case '--help':
568
+ case 'help':
569
+ console.log(usage());
570
+ break;
571
+ case 'render':
572
+ commandRender(args);
573
+ break;
574
+ case 'deliver':
575
+ await commandDeliver(args);
576
+ break;
577
+ case 'validate':
578
+ commandValidate(args);
579
+ break;
580
+ case 'inspect':
581
+ commandValidate([...args, '--layout-json']);
582
+ break;
583
+ case 'check':
584
+ commandCheck(args);
585
+ break;
586
+ case 'guide':
587
+ await commandGuide(args);
588
+ break;
589
+ case 'examples':
590
+ commandExamples();
591
+ break;
592
+ case 'doctor':
593
+ await commandDoctor();
594
+ break;
595
+ case 'demo':
596
+ commandDemo(args);
597
+ break;
598
+ default:
599
+ fail(`Unknown command "${command}".\n\n${usage()}`);
600
+ }
@@ -0,0 +1,67 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import path from 'node:path';
3
+
4
+ const OPENERS = {
5
+ darwin: {
6
+ command: 'open',
7
+ method: 'open',
8
+ args: (target) => [target],
9
+ },
10
+ linux: {
11
+ command: 'xdg-open',
12
+ method: 'xdg-open',
13
+ args: (target) => [target],
14
+ },
15
+ win32: {
16
+ command: 'powershell.exe',
17
+ method: 'powershell',
18
+ // Keep the command constant and pass the target through PowerShell's
19
+ // argument array. Paths are never interpolated into executable source.
20
+ args: (target) => [
21
+ '-NoProfile',
22
+ '-NonInteractive',
23
+ '-Command',
24
+ 'Start-Process -FilePath $args[0]',
25
+ target,
26
+ ],
27
+ },
28
+ };
29
+
30
+ export function openArtifact(target, options = {}) {
31
+ const absoluteTarget = path.resolve(target);
32
+ const platform = options.platform || process.platform;
33
+ const opener = OPENERS[platform];
34
+ if (!opener) {
35
+ return {
36
+ requested: true,
37
+ status: 'unsupported',
38
+ target: absoluteTarget,
39
+ method: null,
40
+ };
41
+ }
42
+
43
+ const spawn = options.spawn || spawnSync;
44
+ let result;
45
+ try {
46
+ result = spawn(opener.command, opener.args(absoluteTarget), {
47
+ encoding: 'utf8',
48
+ shell: false,
49
+ stdio: 'ignore',
50
+ timeout: options.timeoutMs || 5000,
51
+ windowsHide: true,
52
+ });
53
+ } catch {
54
+ result = { error: new Error('opener threw') };
55
+ }
56
+
57
+ let status = 'opened';
58
+ if (result?.error?.code === 'ENOENT') status = 'unsupported';
59
+ else if (result?.error || result?.signal || result?.status !== 0) status = 'failed';
60
+
61
+ return {
62
+ requested: true,
63
+ status,
64
+ target: absoluteTarget,
65
+ method: opener.method,
66
+ };
67
+ }