@jaimevalasek/aioson 1.4.0 → 1.5.1
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/CHANGELOG.md +31 -1
- package/LICENSE +661 -21
- package/README.md +3 -1
- package/docs/en/squad-dashboard.md +372 -0
- package/docs/openclaw-bridge.md +308 -0
- package/docs/pt/agentes.md +124 -10
- package/docs/pt/cenarios.md +46 -2
- package/docs/pt/comandos-cli.md +60 -1
- package/docs/pt/inicio-rapido.md +18 -2
- package/docs/pt/squad-dashboard.md +373 -0
- package/docs/testing/genome-2.0-matrix.md +5 -5
- package/docs/testing/genome-2.0-rollout.md +9 -9
- package/package.json +2 -2
- package/src/backup-local.js +74 -0
- package/src/cli.js +98 -0
- package/src/commands/backup-local-cmd.js +25 -0
- package/src/commands/runtime.js +242 -0
- package/src/commands/setup-context.js +7 -2
- package/src/commands/squad-daemon.js +209 -0
- package/src/commands/squad-dashboard.js +39 -0
- package/src/commands/squad-deploy.js +64 -0
- package/src/commands/squad-doctor.js +52 -0
- package/src/commands/squad-mcp.js +270 -0
- package/src/commands/squad-processes.js +56 -0
- package/src/commands/squad-recovery.js +42 -0
- package/src/commands/squad-roi.js +291 -0
- package/src/commands/squad-score.js +250 -0
- package/src/commands/squad-status.js +37 -1
- package/src/commands/squad-validate.js +62 -1
- package/src/commands/squad-webhook.js +160 -0
- package/src/commands/squad-worker.js +191 -0
- package/src/commands/squad-worktrees.js +75 -0
- package/src/commands/web-map.js +70 -0
- package/src/commands/web-scrape.js +71 -0
- package/src/constants.js +8 -0
- package/src/context-writer.js +45 -1
- package/src/i18n/messages/en.js +127 -1
- package/src/i18n/messages/es.js +117 -0
- package/src/i18n/messages/fr.js +117 -0
- package/src/i18n/messages/pt-BR.js +126 -1
- package/src/lib/webhook-server.js +328 -0
- package/src/mcp-connectors/registry.js +602 -0
- package/src/runtime-store.js +259 -2
- package/src/squad/external-session.js +180 -0
- package/src/squad/inter-squad.js +74 -0
- package/src/squad/recovery-context.js +201 -0
- package/src/squad/worktree-manager.js +114 -0
- package/src/squad-daemon.js +490 -0
- package/src/squad-dashboard/api.js +223 -0
- package/src/squad-dashboard/attachment-handler.js +93 -0
- package/src/squad-dashboard/context-monitor.js +157 -0
- package/src/squad-dashboard/execution-logs.js +115 -0
- package/src/squad-dashboard/hunk-review.js +209 -0
- package/src/squad-dashboard/metrics.js +133 -0
- package/src/squad-dashboard/process-monitor.js +125 -0
- package/src/squad-dashboard/renderer.js +858 -0
- package/src/squad-dashboard/server.js +232 -0
- package/src/squad-dashboard/styles.js +525 -0
- package/src/squad-dashboard/token-tracker.js +99 -0
- package/src/web.js +284 -0
- package/src/worker-runner.js +339 -0
- package/template/.aioson/agents/analyst.md +4 -0
- package/template/.aioson/agents/architect.md +4 -0
- package/template/.aioson/agents/dev.md +120 -11
- package/template/.aioson/agents/deyvin.md +8 -0
- package/template/.aioson/agents/neo.md +152 -0
- package/template/.aioson/agents/orache.md +17 -0
- package/template/.aioson/agents/orchestrator.md +26 -0
- package/template/.aioson/agents/product.md +60 -12
- package/template/.aioson/agents/qa.md +1 -0
- package/template/.aioson/agents/setup.md +63 -19
- package/template/.aioson/agents/sheldon.md +603 -0
- package/template/.aioson/agents/squad.md +191 -0
- package/template/.aioson/agents/tester.md +254 -0
- package/template/.aioson/agents/ux-ui.md +12 -0
- package/template/.aioson/config.md +6 -0
- package/template/.aioson/locales/en/agents/analyst.md +8 -0
- package/template/.aioson/locales/en/agents/architect.md +8 -0
- package/template/.aioson/locales/en/agents/dev.md +66 -7
- package/template/.aioson/locales/en/agents/deyvin.md +8 -0
- package/template/.aioson/locales/en/agents/neo.md +8 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/en/agents/qa.md +49 -0
- package/template/.aioson/locales/en/agents/setup.md +2 -1
- package/template/.aioson/locales/en/agents/sheldon.md +340 -0
- package/template/.aioson/locales/en/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/es/agents/analyst.md +8 -0
- package/template/.aioson/locales/es/agents/architect.md +8 -0
- package/template/.aioson/locales/es/agents/dev.md +66 -7
- package/template/.aioson/locales/es/agents/deyvin.md +8 -0
- package/template/.aioson/locales/es/agents/neo.md +48 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/es/agents/qa.md +26 -0
- package/template/.aioson/locales/es/agents/setup.md +2 -1
- package/template/.aioson/locales/es/agents/sheldon.md +192 -0
- package/template/.aioson/locales/es/agents/squad.md +63 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/fr/agents/analyst.md +8 -0
- package/template/.aioson/locales/fr/agents/architect.md +8 -0
- package/template/.aioson/locales/fr/agents/dev.md +66 -7
- package/template/.aioson/locales/fr/agents/deyvin.md +8 -0
- package/template/.aioson/locales/fr/agents/neo.md +48 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/fr/agents/qa.md +26 -0
- package/template/.aioson/locales/fr/agents/setup.md +2 -1
- package/template/.aioson/locales/fr/agents/sheldon.md +192 -0
- package/template/.aioson/locales/fr/agents/squad.md +63 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +19 -0
- package/template/.aioson/locales/pt-BR/agents/architect.md +19 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +75 -12
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +8 -0
- package/template/.aioson/locales/pt-BR/agents/neo.md +147 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +8 -3
- package/template/.aioson/locales/pt-BR/agents/qa.md +60 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +2 -1
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +192 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +105 -0
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +8 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +21 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +178 -1
- package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -0
- package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -0
- package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +55 -9
- package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +1 -1
- package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +100 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +43 -9
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +40 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +1 -1
- package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +99 -12
- package/template/.aioson/skills/design/warm-craft-ui/SKILL.md +209 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/art-direction.md +324 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/components.md +508 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/dashboards.md +223 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/design-tokens.md +374 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/motion.md +356 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/patterns.md +288 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/websites.md +289 -0
- package/template/.aioson/skills/premium-visual-design/SKILL.md +83 -0
- package/template/.aioson/skills/premium-visual-design/components/agent-badge.md +92 -0
- package/template/.aioson/skills/premium-visual-design/components/dependency-node.md +102 -0
- package/template/.aioson/skills/premium-visual-design/components/mention-autocomplete.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/notification-center.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/review-action-bar.md +188 -0
- package/template/.aioson/skills/premium-visual-design/components/team-switcher.md +131 -0
- package/template/.aioson/skills/premium-visual-design/patterns/agent-message-thread.md +198 -0
- package/template/.aioson/skills/premium-visual-design/patterns/notification-panel.md +275 -0
- package/template/.aioson/skills/premium-visual-design/patterns/review-workflow-ui.md +234 -0
- package/template/.aioson/skills/premium-visual-design/patterns/task-dependency-graph.md +147 -0
- package/template/.aioson/skills/premium-visual-design/tokens/status-extended.md +142 -0
- package/template/.aioson/skills/squad/formats/catalog.json +15 -0
- package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -0
- package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -0
- package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -0
- package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -0
- package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -0
- package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -0
- package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -0
- package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -0
- package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -0
- package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -0
- package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -0
- package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -0
- package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -0
- package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -0
- package/template/.aioson/skills/squad/references/checklist-templates.md +122 -0
- package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -0
- package/template/.aioson/skills/squad/references/workflow-templates.md +169 -0
- package/template/.aioson/skills/static/debugging-protocol.md +42 -0
- package/template/.aioson/skills/static/git-worktrees.md +36 -0
- package/template/.aioson/tasks/implementation-plan.md +19 -0
- package/template/.aioson/tasks/squad-design.md +28 -0
- package/template/.aioson/tasks/squad-profile.md +48 -0
- package/template/.aioson/tasks/squad-review.md +61 -0
- package/template/.aioson/tasks/squad-task-decompose.md +66 -0
- package/template/.claude/commands/aioson/agent/neo.md +5 -0
- package/template/.claude/commands/aioson/agent/tester.md +5 -0
- package/template/.gemini/GEMINI.md +1 -0
- package/template/.gemini/commands/aios-neo.toml +4 -0
- package/template/.gemini/commands/aios-tester.toml +6 -0
- package/template/AGENTS.md +3 -0
- package/template/CLAUDE.md +5 -2
- package/template/OPENCODE.md +2 -0
package/src/web.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 10000;
|
|
4
|
+
const DEFAULT_MAX_HTML_CHARS = 500000;
|
|
5
|
+
const DEFAULT_USER_AGENT = 'AIOSON-Web/1.0 (+https://aioson.dev)';
|
|
6
|
+
|
|
7
|
+
function ensureValidUrl(input) {
|
|
8
|
+
try {
|
|
9
|
+
return new URL(String(input || '').trim());
|
|
10
|
+
} catch {
|
|
11
|
+
throw new Error(`Invalid URL: ${input || ''}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeUrl(input) {
|
|
16
|
+
const url = input instanceof URL ? new URL(input.toString()) : ensureValidUrl(input);
|
|
17
|
+
url.hash = '';
|
|
18
|
+
if ((url.protocol === 'http:' && url.port === '80') || (url.protocol === 'https:' && url.port === '443')) {
|
|
19
|
+
url.port = '';
|
|
20
|
+
}
|
|
21
|
+
const normalized = url.toString();
|
|
22
|
+
return normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function decodeHtmlEntities(text) {
|
|
26
|
+
return String(text || '')
|
|
27
|
+
.replace(/&#(\d+);/g, (_, code) => String.fromCodePoint(Number(code)))
|
|
28
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, code) => String.fromCodePoint(parseInt(code, 16)))
|
|
29
|
+
.replace(/ /gi, ' ')
|
|
30
|
+
.replace(/&/gi, '&')
|
|
31
|
+
.replace(/"/gi, '"')
|
|
32
|
+
.replace(/'/gi, "'")
|
|
33
|
+
.replace(/</gi, '<')
|
|
34
|
+
.replace(/>/gi, '>');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function stripTags(html) {
|
|
38
|
+
return decodeHtmlEntities(String(html || '').replace(/<[^>]+>/g, ' '));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function cleanWhitespace(text) {
|
|
42
|
+
return String(text || '')
|
|
43
|
+
.replace(/\r/g, '')
|
|
44
|
+
.replace(/\t/g, ' ')
|
|
45
|
+
.replace(/[ \u00A0]+\n/g, '\n')
|
|
46
|
+
.replace(/\n[ \u00A0]+/g, '\n')
|
|
47
|
+
.replace(/[ \u00A0]{2,}/g, ' ')
|
|
48
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
49
|
+
.trim();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function extractTagContent(html, tagName) {
|
|
53
|
+
const match = String(html || '').match(new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)<\\/${tagName}>`, 'i'));
|
|
54
|
+
return match ? match[1] : '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractTitle(html) {
|
|
58
|
+
return cleanWhitespace(stripTags(extractTagContent(html, 'title')));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function extractMetaContent(html, name) {
|
|
62
|
+
const match = String(html || '').match(new RegExp(`<meta[^>]+(?:name|property)=['\"]${name}['\"][^>]+content=['\"]([^'\"]+)['\"][^>]*>`, 'i'));
|
|
63
|
+
return match ? decodeHtmlEntities(match[1]).trim() : '';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function extractCanonical(html) {
|
|
67
|
+
const match = String(html || '').match(/<link[^>]+rel=['\"]canonical['\"][^>]+href=['\"]([^'\"]+)['\"][^>]*>/i);
|
|
68
|
+
return match ? match[1].trim() : '';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function extractMainHtml(html) {
|
|
72
|
+
const source = String(html || '');
|
|
73
|
+
const patterns = [
|
|
74
|
+
/<main\b[^>]*>([\s\S]*?)<\/main>/i,
|
|
75
|
+
/<article\b[^>]*>([\s\S]*?)<\/article>/i,
|
|
76
|
+
/<div\b[^>]*role=['\"]main['\"][^>]*>([\s\S]*?)<\/div>/i,
|
|
77
|
+
/<body\b[^>]*>([\s\S]*?)<\/body>/i
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
for (const pattern of patterns) {
|
|
81
|
+
const match = source.match(pattern);
|
|
82
|
+
if (match && match[1]) return match[1];
|
|
83
|
+
}
|
|
84
|
+
return source;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function htmlToMarkdown(html) {
|
|
88
|
+
let output = String(html || '');
|
|
89
|
+
output = output.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ');
|
|
90
|
+
output = output.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ');
|
|
91
|
+
output = output.replace(/<noscript\b[^>]*>[\s\S]*?<\/noscript>/gi, ' ');
|
|
92
|
+
output = output.replace(/<svg\b[^>]*>[\s\S]*?<\/svg>/gi, ' ');
|
|
93
|
+
output = output.replace(/<h1\b[^>]*>([\s\S]*?)<\/h1>/gi, '\n# $1\n\n');
|
|
94
|
+
output = output.replace(/<h2\b[^>]*>([\s\S]*?)<\/h2>/gi, '\n## $1\n\n');
|
|
95
|
+
output = output.replace(/<h3\b[^>]*>([\s\S]*?)<\/h3>/gi, '\n### $1\n\n');
|
|
96
|
+
output = output.replace(/<h4\b[^>]*>([\s\S]*?)<\/h4>/gi, '\n#### $1\n\n');
|
|
97
|
+
output = output.replace(/<h5\b[^>]*>([\s\S]*?)<\/h5>/gi, '\n##### $1\n\n');
|
|
98
|
+
output = output.replace(/<h6\b[^>]*>([\s\S]*?)<\/h6>/gi, '\n###### $1\n\n');
|
|
99
|
+
output = output.replace(/<li\b[^>]*>([\s\S]*?)<\/li>/gi, '\n- $1');
|
|
100
|
+
output = output.replace(/<(p|div|section|article|main|header|footer|aside|blockquote)\b[^>]*>/gi, '\n');
|
|
101
|
+
output = output.replace(/<\/(p|div|section|article|main|header|footer|aside|blockquote)>/gi, '\n');
|
|
102
|
+
output = output.replace(/<br\s*\/?>/gi, '\n');
|
|
103
|
+
output = output.replace(/<a\b[^>]*href=['\"]([^'\"]+)['\"][^>]*>([\s\S]*?)<\/a>/gi, '$2 ($1)');
|
|
104
|
+
output = stripTags(output);
|
|
105
|
+
return cleanWhitespace(output);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function htmlToText(html) {
|
|
109
|
+
return cleanWhitespace(stripTags(String(html || '')
|
|
110
|
+
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ')
|
|
111
|
+
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, ' ')
|
|
112
|
+
.replace(/<noscript\b[^>]*>[\s\S]*?<\/noscript>/gi, ' ')
|
|
113
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
114
|
+
.replace(/<\/(p|div|section|article|main|header|footer|aside|li|ul|ol|h1|h2|h3|h4|h5|h6)>/gi, '\n')));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolveHref(href, pageUrl) {
|
|
118
|
+
const value = String(href || '').trim();
|
|
119
|
+
if (!value || value.startsWith('#') || value.startsWith('mailto:') || value.startsWith('tel:') || value.startsWith('javascript:')) {
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
return normalizeUrl(new URL(value, pageUrl));
|
|
125
|
+
} catch {
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function extractLinks(html, pageUrl, options = {}) {
|
|
131
|
+
const sameOriginOnly = options.sameOriginOnly !== false;
|
|
132
|
+
const base = ensureValidUrl(pageUrl);
|
|
133
|
+
const links = new Set();
|
|
134
|
+
const pattern = /<a\b[^>]*href=['\"]([^'\"]+)['\"][^>]*>/gi;
|
|
135
|
+
let match;
|
|
136
|
+
|
|
137
|
+
while ((match = pattern.exec(String(html || ''))) !== null) {
|
|
138
|
+
const normalized = resolveHref(match[1], base);
|
|
139
|
+
if (!normalized) continue;
|
|
140
|
+
if (sameOriginOnly) {
|
|
141
|
+
try {
|
|
142
|
+
if (new URL(normalized).origin !== base.origin) continue;
|
|
143
|
+
} catch {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
links.add(normalized);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return Array.from(links).sort();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function fetchPage(url, options = {}) {
|
|
154
|
+
const timeoutMs = Number(options.timeoutMs) > 0 ? Number(options.timeoutMs) : DEFAULT_TIMEOUT_MS;
|
|
155
|
+
const maxHtmlChars = Number(options.maxHtmlChars) > 0 ? Number(options.maxHtmlChars) : DEFAULT_MAX_HTML_CHARS;
|
|
156
|
+
const headers = {
|
|
157
|
+
'user-agent': options.userAgent || DEFAULT_USER_AGENT,
|
|
158
|
+
'accept': 'text/html,application/xhtml+xml,text/plain;q=0.9,*/*;q=0.8',
|
|
159
|
+
...(options.headers || {})
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const response = await fetch(String(url), {
|
|
163
|
+
method: 'GET',
|
|
164
|
+
headers,
|
|
165
|
+
redirect: 'follow',
|
|
166
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const finalUrl = normalizeUrl(response.url || url);
|
|
170
|
+
const contentType = response.headers.get('content-type') || '';
|
|
171
|
+
const rawText = await response.text();
|
|
172
|
+
const html = rawText.length > maxHtmlChars ? rawText.slice(0, maxHtmlChars) : rawText;
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
ok: response.ok,
|
|
176
|
+
url: finalUrl,
|
|
177
|
+
statusCode: response.status,
|
|
178
|
+
contentType,
|
|
179
|
+
html,
|
|
180
|
+
truncated: rawText.length > html.length
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function scrapePage(url, options = {}) {
|
|
185
|
+
const page = await fetchPage(url, options);
|
|
186
|
+
const mainHtml = extractMainHtml(page.html);
|
|
187
|
+
const title = extractTitle(page.html);
|
|
188
|
+
const description = extractMetaContent(page.html, 'description') || extractMetaContent(page.html, 'og:description');
|
|
189
|
+
const canonical = extractCanonical(page.html);
|
|
190
|
+
const links = extractLinks(page.html, page.url, { sameOriginOnly: options.sameOriginOnly !== false });
|
|
191
|
+
const text = htmlToText(mainHtml);
|
|
192
|
+
const markdown = htmlToMarkdown(mainHtml);
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
ok: page.ok,
|
|
196
|
+
url: page.url,
|
|
197
|
+
statusCode: page.statusCode,
|
|
198
|
+
contentType: page.contentType,
|
|
199
|
+
title,
|
|
200
|
+
description,
|
|
201
|
+
canonical,
|
|
202
|
+
html: mainHtml,
|
|
203
|
+
text,
|
|
204
|
+
markdown,
|
|
205
|
+
links,
|
|
206
|
+
truncated: page.truncated
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function mapWebsite(url, options = {}) {
|
|
211
|
+
const startUrl = normalizeUrl(url);
|
|
212
|
+
const start = ensureValidUrl(startUrl);
|
|
213
|
+
const maxDepth = Number(options.maxDepth) >= 0 ? Number(options.maxDepth) : 2;
|
|
214
|
+
const maxPages = Number(options.maxPages) > 0 ? Number(options.maxPages) : 25;
|
|
215
|
+
const sameOriginOnly = options.sameOriginOnly !== false;
|
|
216
|
+
|
|
217
|
+
const queue = [{ url: startUrl, depth: 0 }];
|
|
218
|
+
const visited = new Set();
|
|
219
|
+
const pages = [];
|
|
220
|
+
|
|
221
|
+
while (queue.length > 0 && visited.size < maxPages) {
|
|
222
|
+
const current = queue.shift();
|
|
223
|
+
if (!current || visited.has(current.url)) continue;
|
|
224
|
+
visited.add(current.url);
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const page = await scrapePage(current.url, options);
|
|
228
|
+
const sameOriginLinks = extractLinks(page.html || '', page.url, { sameOriginOnly });
|
|
229
|
+
pages.push({
|
|
230
|
+
url: page.url,
|
|
231
|
+
depth: current.depth,
|
|
232
|
+
statusCode: page.statusCode,
|
|
233
|
+
title: page.title,
|
|
234
|
+
description: page.description,
|
|
235
|
+
linkCount: sameOriginLinks.length
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (current.depth >= maxDepth) continue;
|
|
239
|
+
for (const link of sameOriginLinks) {
|
|
240
|
+
if (visited.has(link)) continue;
|
|
241
|
+
if (sameOriginOnly) {
|
|
242
|
+
try {
|
|
243
|
+
if (new URL(link).origin != start.origin) continue;
|
|
244
|
+
} catch {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
queue.push({ url: link, depth: current.depth + 1 });
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
pages.push({
|
|
252
|
+
url: current.url,
|
|
253
|
+
depth: current.depth,
|
|
254
|
+
statusCode: 0,
|
|
255
|
+
title: '',
|
|
256
|
+
description: '',
|
|
257
|
+
linkCount: 0,
|
|
258
|
+
error: error.message
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
startUrl,
|
|
265
|
+
maxDepth,
|
|
266
|
+
maxPages,
|
|
267
|
+
pageCount: pages.length,
|
|
268
|
+
urls: pages.map((item) => item.url),
|
|
269
|
+
pages
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = {
|
|
274
|
+
DEFAULT_TIMEOUT_MS,
|
|
275
|
+
DEFAULT_MAX_HTML_CHARS,
|
|
276
|
+
DEFAULT_USER_AGENT,
|
|
277
|
+
normalizeUrl,
|
|
278
|
+
extractLinks,
|
|
279
|
+
htmlToMarkdown,
|
|
280
|
+
htmlToText,
|
|
281
|
+
fetchPage,
|
|
282
|
+
scrapePage,
|
|
283
|
+
mapWebsite
|
|
284
|
+
};
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { spawn } = require('node:child_process');
|
|
6
|
+
|
|
7
|
+
const SQUADS_DIR = path.join('.aioson', 'squads');
|
|
8
|
+
const DEFAULT_TIMEOUT = 30000;
|
|
9
|
+
const DEFAULT_RETRY_ATTEMPTS = 3;
|
|
10
|
+
const BACKOFF_BASE = [1000, 3000, 8000];
|
|
11
|
+
|
|
12
|
+
async function pathExists(targetPath) {
|
|
13
|
+
try {
|
|
14
|
+
await fs.access(targetPath);
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function loadWorkerConfig(projectDir, squadSlug, workerSlug) {
|
|
22
|
+
const configPath = path.join(projectDir, SQUADS_DIR, squadSlug, 'workers', workerSlug, 'worker.json');
|
|
23
|
+
if (!(await pathExists(configPath))) return null;
|
|
24
|
+
const raw = await fs.readFile(configPath, 'utf8');
|
|
25
|
+
return JSON.parse(raw);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function listWorkers(projectDir, squadSlug) {
|
|
29
|
+
const workersDir = path.join(projectDir, SQUADS_DIR, squadSlug, 'workers');
|
|
30
|
+
let entries;
|
|
31
|
+
try {
|
|
32
|
+
entries = await fs.readdir(workersDir, { withFileTypes: true });
|
|
33
|
+
} catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const workers = [];
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
if (!entry.isDirectory()) continue;
|
|
39
|
+
const config = await loadWorkerConfig(projectDir, squadSlug, entry.name);
|
|
40
|
+
if (config) {
|
|
41
|
+
workers.push({ slug: entry.name, ...config });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return workers;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveScript(projectDir, squadSlug, workerSlug) {
|
|
48
|
+
const base = path.join(projectDir, SQUADS_DIR, squadSlug, 'workers', workerSlug);
|
|
49
|
+
return {
|
|
50
|
+
js: path.join(base, 'run.js'),
|
|
51
|
+
py: path.join(base, 'run.py')
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function validateInputs(inputPayload, inputSchema) {
|
|
56
|
+
if (!inputSchema) return { valid: true, errors: [] };
|
|
57
|
+
const errors = [];
|
|
58
|
+
for (const [key, spec] of Object.entries(inputSchema)) {
|
|
59
|
+
if (spec.required && (inputPayload[key] === undefined || inputPayload[key] === null)) {
|
|
60
|
+
errors.push(`Missing required input: ${key}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { valid: errors.length === 0, errors };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function spawnWorker(scriptPath, inputPayload, env, timeoutMs) {
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const ext = path.extname(scriptPath);
|
|
69
|
+
const cmd = ext === '.py' ? 'python3' : 'node';
|
|
70
|
+
const args = [scriptPath, JSON.stringify(inputPayload)];
|
|
71
|
+
const child = spawn(cmd, args, {
|
|
72
|
+
env: { ...process.env, ...env },
|
|
73
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
74
|
+
timeout: timeoutMs
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
let stdout = '';
|
|
78
|
+
let stderr = '';
|
|
79
|
+
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
80
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
81
|
+
|
|
82
|
+
child.on('close', (code) => {
|
|
83
|
+
if (code === 0) {
|
|
84
|
+
let output;
|
|
85
|
+
try {
|
|
86
|
+
output = JSON.parse(stdout);
|
|
87
|
+
} catch {
|
|
88
|
+
output = { raw: stdout.trim() };
|
|
89
|
+
}
|
|
90
|
+
resolve({ ok: true, output, stderr: stderr.trim() || null });
|
|
91
|
+
} else {
|
|
92
|
+
let errorDetail;
|
|
93
|
+
try {
|
|
94
|
+
errorDetail = JSON.parse(stderr);
|
|
95
|
+
} catch {
|
|
96
|
+
errorDetail = { message: stderr.trim() || `Process exited with code ${code}` };
|
|
97
|
+
}
|
|
98
|
+
resolve({ ok: false, error: errorDetail.error || errorDetail.message || String(errorDetail), code });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
child.on('error', (err) => {
|
|
103
|
+
if (err.code === 'ETIMEDOUT' || err.killed) {
|
|
104
|
+
resolve({ ok: false, error: `Worker timed out after ${timeoutMs}ms`, code: -1 });
|
|
105
|
+
} else {
|
|
106
|
+
reject(err);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveEnvVars(envKeys) {
|
|
113
|
+
const resolved = {};
|
|
114
|
+
for (const key of (envKeys || [])) {
|
|
115
|
+
if (process.env[key]) {
|
|
116
|
+
resolved[key] = process.env[key];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return resolved;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function sleep(ms) {
|
|
123
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function runWorker(projectDir, squadSlug, workerSlug, inputPayload, options = {}) {
|
|
127
|
+
const config = await loadWorkerConfig(projectDir, squadSlug, workerSlug);
|
|
128
|
+
if (!config) {
|
|
129
|
+
return { ok: false, error: `Worker config not found: ${workerSlug}`, attempts: 0 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Validate inputs
|
|
133
|
+
const validation = validateInputs(inputPayload || {}, config.inputs);
|
|
134
|
+
if (!validation.valid) {
|
|
135
|
+
return { ok: false, error: `Input validation failed: ${validation.errors.join(', ')}`, attempts: 0 };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Resolve script path
|
|
139
|
+
const scripts = resolveScript(projectDir, squadSlug, workerSlug);
|
|
140
|
+
let scriptPath;
|
|
141
|
+
if (await pathExists(scripts.js)) {
|
|
142
|
+
scriptPath = scripts.js;
|
|
143
|
+
} else if (await pathExists(scripts.py)) {
|
|
144
|
+
scriptPath = scripts.py;
|
|
145
|
+
} else {
|
|
146
|
+
return { ok: false, error: `No run script found for worker: ${workerSlug}`, attempts: 0 };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Resolve env vars
|
|
150
|
+
const env = resolveEnvVars(config.env);
|
|
151
|
+
|
|
152
|
+
// Resolve MCP env vars if worker declares uses_mcp
|
|
153
|
+
if (config.uses_mcp && config.uses_mcp.length > 0) {
|
|
154
|
+
try {
|
|
155
|
+
const { listIntegrations, buildWorkerMcpEnv } = require('./mcp-connectors/registry');
|
|
156
|
+
const integrations = await listIntegrations(projectDir, squadSlug);
|
|
157
|
+
const mcpEnv = buildWorkerMcpEnv(projectDir, squadSlug, config.uses_mcp, integrations);
|
|
158
|
+
Object.assign(env, mcpEnv);
|
|
159
|
+
} catch { /* MCP resolution optional — fail silently */ }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Timeout and retry config
|
|
163
|
+
const timeoutMs = config.timeout_ms || DEFAULT_TIMEOUT;
|
|
164
|
+
const maxAttempts = (config.retry && config.retry.attempts) || (options.noRetry ? 1 : DEFAULT_RETRY_ATTEMPTS);
|
|
165
|
+
const triggerType = options.triggerType || 'manual';
|
|
166
|
+
|
|
167
|
+
let lastResult;
|
|
168
|
+
const startTime = Date.now();
|
|
169
|
+
|
|
170
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
171
|
+
lastResult = await spawnWorker(scriptPath, inputPayload || {}, env, timeoutMs);
|
|
172
|
+
lastResult.attempt = attempt;
|
|
173
|
+
lastResult.triggerType = triggerType;
|
|
174
|
+
|
|
175
|
+
if (lastResult.ok) {
|
|
176
|
+
lastResult.durationMs = Date.now() - startTime;
|
|
177
|
+
return lastResult;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Backoff before retry
|
|
181
|
+
if (attempt < maxAttempts) {
|
|
182
|
+
const delay = BACKOFF_BASE[attempt - 1] || BACKOFF_BASE[BACKOFF_BASE.length - 1];
|
|
183
|
+
await sleep(delay);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
lastResult.durationMs = Date.now() - startTime;
|
|
188
|
+
return lastResult;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// --- Scaffold generation ---
|
|
192
|
+
|
|
193
|
+
function generateWorkerJson(slug, name, triggerType, inputFields, outputFields, envVars) {
|
|
194
|
+
const trigger = { type: triggerType || 'manual' };
|
|
195
|
+
if (triggerType === 'event') {
|
|
196
|
+
trigger.source = 'content_item_created';
|
|
197
|
+
trigger.filter = {};
|
|
198
|
+
}
|
|
199
|
+
if (triggerType === 'scheduled') {
|
|
200
|
+
trigger.cron = '0 8 * * *';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const inputs = {};
|
|
204
|
+
for (const field of (inputFields || [])) {
|
|
205
|
+
inputs[field] = { type: 'string', required: true };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const outputs = {};
|
|
209
|
+
for (const field of (outputFields || [])) {
|
|
210
|
+
outputs[field] = { type: 'string' };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
slug,
|
|
215
|
+
name: name || slug,
|
|
216
|
+
type: triggerType || 'manual',
|
|
217
|
+
trigger,
|
|
218
|
+
inputs,
|
|
219
|
+
outputs,
|
|
220
|
+
env: envVars || [],
|
|
221
|
+
timeout_ms: 30000,
|
|
222
|
+
retry: { attempts: 3, backoff: 'exponential' }
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function generateRunJs(slug, config) {
|
|
227
|
+
const inputParsing = Object.keys(config.inputs || {}).length > 0
|
|
228
|
+
? Object.keys(config.inputs).map(k => ` // input.${k}`).join('\n')
|
|
229
|
+
: ' // No inputs defined';
|
|
230
|
+
|
|
231
|
+
const outputReturn = Object.keys(config.outputs || {}).length > 0
|
|
232
|
+
? ' ' + Object.keys(config.outputs).map(k => `${k}: null`).join(',\n ')
|
|
233
|
+
: ' result: "ok"';
|
|
234
|
+
|
|
235
|
+
return `#!/usr/bin/env node
|
|
236
|
+
'use strict';
|
|
237
|
+
|
|
238
|
+
// Worker: ${slug}
|
|
239
|
+
// Trigger: ${config.type || 'manual'}
|
|
240
|
+
// Generated by aioson squad:create
|
|
241
|
+
|
|
242
|
+
const input = JSON.parse(process.argv[2] || '{}');
|
|
243
|
+
|
|
244
|
+
async function execute(input) {
|
|
245
|
+
// Available inputs:
|
|
246
|
+
${inputParsing}
|
|
247
|
+
|
|
248
|
+
// TODO: Implement worker logic here
|
|
249
|
+
// Examples:
|
|
250
|
+
// - Send WhatsApp message via Business API
|
|
251
|
+
// - Call external REST API
|
|
252
|
+
// - Calculate metrics and save to squad_metrics
|
|
253
|
+
// - Move a lead in CRM
|
|
254
|
+
// - Send email via SMTP
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
${outputReturn}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
execute(input)
|
|
262
|
+
.then(result => {
|
|
263
|
+
process.stdout.write(JSON.stringify(result));
|
|
264
|
+
process.exit(0);
|
|
265
|
+
})
|
|
266
|
+
.catch(err => {
|
|
267
|
+
process.stderr.write(JSON.stringify({ error: err.message }));
|
|
268
|
+
process.exit(1);
|
|
269
|
+
});
|
|
270
|
+
`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function generateWorkerReadme(slug, config) {
|
|
274
|
+
const envSection = (config.env || []).length > 0
|
|
275
|
+
? config.env.map(e => `- \`${e}\``).join('\n')
|
|
276
|
+
: '- None';
|
|
277
|
+
|
|
278
|
+
return `# Worker: ${config.name || slug}
|
|
279
|
+
|
|
280
|
+
## Trigger
|
|
281
|
+
- **Type**: ${config.type || 'manual'}
|
|
282
|
+
${config.trigger && config.trigger.cron ? `- **Cron**: \`${config.trigger.cron}\`` : ''}
|
|
283
|
+
${config.trigger && config.trigger.source ? `- **Source**: ${config.trigger.source}` : ''}
|
|
284
|
+
|
|
285
|
+
## Inputs
|
|
286
|
+
${Object.entries(config.inputs || {}).map(([k, v]) => `- \`${k}\` (${v.type})${v.required ? ' *required*' : ''}`).join('\n') || '- None'}
|
|
287
|
+
|
|
288
|
+
## Outputs
|
|
289
|
+
${Object.entries(config.outputs || {}).map(([k, v]) => `- \`${k}\` (${v.type})`).join('\n') || '- None'}
|
|
290
|
+
|
|
291
|
+
## Environment Variables
|
|
292
|
+
${envSection}
|
|
293
|
+
|
|
294
|
+
## Usage
|
|
295
|
+
|
|
296
|
+
\`\`\`bash
|
|
297
|
+
# Run manually
|
|
298
|
+
aioson squad:worker --squad=<squad-slug> --worker=${slug} --input='{"key": "value"}'
|
|
299
|
+
|
|
300
|
+
# Test with dry run
|
|
301
|
+
aioson squad:worker --squad=<squad-slug> --worker=${slug} --sub=test
|
|
302
|
+
|
|
303
|
+
# View execution logs
|
|
304
|
+
aioson squad:worker --squad=<squad-slug> --sub=logs
|
|
305
|
+
\`\`\`
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function scaffoldWorker(projectDir, squadSlug, workerSlug, options = {}) {
|
|
310
|
+
const workerDir = path.join(projectDir, SQUADS_DIR, squadSlug, 'workers', workerSlug);
|
|
311
|
+
await fs.mkdir(workerDir, { recursive: true });
|
|
312
|
+
|
|
313
|
+
const config = generateWorkerJson(
|
|
314
|
+
workerSlug,
|
|
315
|
+
options.name,
|
|
316
|
+
options.triggerType,
|
|
317
|
+
options.inputs,
|
|
318
|
+
options.outputs,
|
|
319
|
+
options.env
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
await fs.writeFile(path.join(workerDir, 'worker.json'), JSON.stringify(config, null, 2));
|
|
323
|
+
await fs.writeFile(path.join(workerDir, 'run.js'), generateRunJs(workerSlug, config));
|
|
324
|
+
await fs.writeFile(path.join(workerDir, 'README.md'), generateWorkerReadme(workerSlug, config));
|
|
325
|
+
|
|
326
|
+
return { workerDir, config };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports = {
|
|
330
|
+
loadWorkerConfig,
|
|
331
|
+
listWorkers,
|
|
332
|
+
runWorker,
|
|
333
|
+
scaffoldWorker,
|
|
334
|
+
generateWorkerJson,
|
|
335
|
+
generateRunJs,
|
|
336
|
+
generateWorkerReadme,
|
|
337
|
+
validateInputs,
|
|
338
|
+
resolveEnvVars
|
|
339
|
+
};
|
|
@@ -226,6 +226,9 @@ The `@analyst` owns all technical and structural content: requirements, entities
|
|
|
226
226
|
Copy, interface text, onboarding messages, and marketing content are not within `@analyst` scope.
|
|
227
227
|
|
|
228
228
|
## Output contract
|
|
229
|
+
|
|
230
|
+
> **CRITICAL — FILE WRITE RULE:** Every artifact listed below MUST be written to disk using the Write tool before this agent session ends. Generating content as chat text is NOT sufficient. Always write the file, then confirm it was saved with: `✅ discovery.md written — @architect can proceed.`
|
|
231
|
+
|
|
229
232
|
Generate `.aioson/context/discovery.md` with the following sections:
|
|
230
233
|
|
|
231
234
|
1. **What we are building** — 2–3 objective lines
|
|
@@ -249,4 +252,5 @@ Generate `.aioson/context/discovery.md` with the following sections:
|
|
|
249
252
|
- Do not finalize any output file with missing or assumed fields.
|
|
250
253
|
- In feature mode: never duplicate content already in `discovery.md` — only document what is new or changed.
|
|
251
254
|
- If `readiness.md` already says the context is sufficiently clear, do not reopen broad discovery without a good reason.
|
|
255
|
+
- At session end, after writing the discovery file, register the session: `aioson agent:done . --agent=analyst --summary="<one-line summary of discovery produced>" 2>/dev/null || true`
|
|
252
256
|
- If `aioson` CLI is not available, write a devlog at session end following the "Devlog" section in `.aioson/config.md`.
|
|
@@ -204,6 +204,9 @@ indexer/ ← subgraph or equivalent
|
|
|
204
204
|
```
|
|
205
205
|
|
|
206
206
|
## Output contract
|
|
207
|
+
|
|
208
|
+
> **CRITICAL — FILE WRITE RULE:** Every artifact listed below MUST be written to disk using the Write tool before this agent session ends. Generating content as chat text is NOT sufficient. Always write the file, then confirm it was saved with: `✅ architecture.md written — @ux-ui or @dev can proceed.`
|
|
209
|
+
|
|
207
210
|
Generate `.aioson/context/architecture.md` with:
|
|
208
211
|
|
|
209
212
|
1. **Architecture overview** — 2–3 lines on the approach
|
|
@@ -233,4 +236,5 @@ Keep architecture.md proportional — verbose output costs tokens without adding
|
|
|
233
236
|
- Ensure output can be executed directly by `@dev` without ambiguity.
|
|
234
237
|
- Do not introduce patterns that do not exist in the chosen stack's conventions.
|
|
235
238
|
- Do not copy content from discovery.md into architecture.md. Reference sections by name: "see discovery.md § Entities". The document chain is already in context.
|
|
239
|
+
- At session end, after writing the architecture file, register the session: `aioson agent:done . --agent=architect --summary="<one-line summary of architecture produced>" 2>/dev/null || true`
|
|
236
240
|
- If `aioson` CLI is not available, write a devlog at session end following the "Devlog" section in `.aioson/config.md`.
|