@hybridlabor-api/aos 4.5.0 → 4.5.2
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/.claude/hooks/memb-inject.mjs +135 -40
- package/.claude/workflows/startcycle-dispatch.mjs +318 -3
- package/.opencode/plugins/bdb-aos.js +171 -0
- package/bin/aos-dashboard.mjs +8 -2
- package/installer.js +307 -12
- package/package.json +1 -1
- package/skills/global_config/aos-setup/SKILL.md +7 -9
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +43 -1
- package/skills/global_config/openwiki-skill/scripts/install_daemon.ps1 +107 -18
- package/.agents/skills/firecrawl/SKILL.md +0 -149
- package/.agents/skills/firecrawl/rules/install.md +0 -82
- package/.agents/skills/firecrawl/rules/security.md +0 -26
- package/.agents/skills/firecrawl-agent/SKILL.md +0 -58
- package/.agents/skills/firecrawl-build/SKILL.md +0 -39
- package/.agents/skills/firecrawl-build-interact/SKILL.md +0 -68
- package/.agents/skills/firecrawl-build-onboarding/SKILL.md +0 -103
- package/.agents/skills/firecrawl-build-onboarding/references/auth-flow.md +0 -39
- package/.agents/skills/firecrawl-build-onboarding/references/project-setup.md +0 -20
- package/.agents/skills/firecrawl-build-onboarding/references/sdk-installation.md +0 -17
- package/.agents/skills/firecrawl-build-scrape/SKILL.md +0 -69
- package/.agents/skills/firecrawl-build-search/SKILL.md +0 -69
- package/.agents/skills/firecrawl-crawl/SKILL.md +0 -59
- package/.agents/skills/firecrawl-download/SKILL.md +0 -70
- package/.agents/skills/firecrawl-interact/SKILL.md +0 -84
- package/.agents/skills/firecrawl-map/SKILL.md +0 -51
- package/.agents/skills/firecrawl-scrape/SKILL.md +0 -69
- package/.agents/skills/firecrawl-search/SKILL.md +0 -60
- package/mcps/RhinoMCP/cc-plugin/.claude/settings.json +0 -10
- package/mcps/after-effects-mcp/build/index.js +0 -840
- package/mcps/after-effects-mcp/build/scripts/applyEffect.jsx +0 -153
- package/mcps/after-effects-mcp/build/scripts/applyEffectTemplate.jsx +0 -218
- package/mcps/after-effects-mcp/build/scripts/createComposition.jsx +0 -71
- package/mcps/after-effects-mcp/build/scripts/createShapeLayer.jsx +0 -147
- package/mcps/after-effects-mcp/build/scripts/createSolidLayer.jsx +0 -114
- package/mcps/after-effects-mcp/build/scripts/createTextLayer.jsx +0 -115
- package/mcps/after-effects-mcp/build/scripts/getLayerInfo.jsx +0 -192
- package/mcps/after-effects-mcp/build/scripts/getProjectInfo.jsx +0 -90
- package/mcps/after-effects-mcp/build/scripts/listCompositions.jsx +0 -50
- package/mcps/after-effects-mcp/build/scripts/mcp-bridge-auto.jsx +0 -1773
- package/mcps/after-effects-mcp/build/scripts/setLayerProperties.jsx +0 -160
- package/mcps/computer-use-mcp/dist/client.d.ts +0 -150
- package/mcps/computer-use-mcp/dist/client.js +0 -136
- package/mcps/computer-use-mcp/dist/entrypoint.d.ts +0 -16
- package/mcps/computer-use-mcp/dist/entrypoint.js +0 -26
- package/mcps/computer-use-mcp/dist/native.d.ts +0 -212
- package/mcps/computer-use-mcp/dist/native.js +0 -50
- package/mcps/computer-use-mcp/dist/server.d.ts +0 -32
- package/mcps/computer-use-mcp/dist/server.js +0 -342
- package/mcps/computer-use-mcp/dist/session.d.ts +0 -101
- package/mcps/computer-use-mcp/dist/session.js +0 -2372
|
@@ -1,60 +1,124 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// aos-hook-version:
|
|
2
|
+
// aos-hook-version: 3
|
|
3
3
|
/**
|
|
4
|
-
* memB ambient memory hook for Claude Code
|
|
4
|
+
* memB ambient memory hook for Claude Code, Google Antigravity, and OpenAI Codex.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Stamped with aos-hook-version for /aos-setup doctor validation.
|
|
7
|
+
* Reads ~/.MemBDB/memb.db directly through node:sqlite and injects relevant
|
|
8
|
+
* project & user memories as ephemeral context in tri-format JSON:
|
|
9
|
+
* - hookSpecificOutput (Claude Code UserPromptSubmit)
|
|
10
|
+
* - injectSteps (Google Antigravity PreInvocation)
|
|
11
|
+
* - systemMessage (OpenAI Codex UserPromptSubmit)
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
* relevant memories as ephemeral additionalContext — nothing is written to
|
|
13
|
-
* disk and nothing lands in git. Fails open: any error exits 0 silently, so a
|
|
14
|
-
* broken memory store can never block a prompt.
|
|
15
|
-
*
|
|
16
|
-
* Install: copy to ~/.claude/hooks/memb-inject.mjs and wire it as a
|
|
17
|
-
* UserPromptSubmit hook (see the aos-setup skill, section 4).
|
|
18
|
-
*
|
|
19
|
-
* Optional: put standing facts (persona, brand, house rules) one per line in
|
|
20
|
-
* ~/.MemBDB/ambient-persona.txt — they are prepended to every prompt.
|
|
13
|
+
* Fails open: any error exits 0 silently.
|
|
21
14
|
*/
|
|
22
15
|
|
|
23
16
|
import { readFileSync, existsSync } from 'node:fs';
|
|
24
17
|
import os from 'node:os';
|
|
25
18
|
import path from 'node:path';
|
|
26
19
|
|
|
27
|
-
// node:sqlite is imported dynamically INSIDE the try below, never at top level:
|
|
28
|
-
// it was flagged behind --experimental-sqlite until Node 22.13, and a top-level
|
|
29
|
-
// import that throws escapes this file's try/catch entirely — surfacing a hook
|
|
30
|
-
// error on every prompt instead of failing open, which is the one thing this
|
|
31
|
-
// hook must never do.
|
|
32
|
-
|
|
33
20
|
const COLLECTION = 'bdb_agent_memory';
|
|
34
21
|
const failOpen = () => process.exit(0);
|
|
35
22
|
|
|
23
|
+
// Helper to find project root by walking upward from candidate start directory
|
|
24
|
+
function findProjectRoot(startDir, homeDir) {
|
|
25
|
+
let current = path.resolve(startDir);
|
|
26
|
+
let bestRoot = null;
|
|
27
|
+
while (current && current !== path.dirname(current)) {
|
|
28
|
+
if (current === homeDir) break;
|
|
29
|
+
// .aos/project.json is the highest-priority project anchor
|
|
30
|
+
if (existsSync(path.join(current, '.aos', 'project.json'))) return current;
|
|
31
|
+
// .git, .agents, or package.json markers
|
|
32
|
+
if (existsSync(path.join(current, '.git')) || existsSync(path.join(current, '.agents')) || existsSync(path.join(current, 'package.json'))) {
|
|
33
|
+
bestRoot = current;
|
|
34
|
+
}
|
|
35
|
+
current = path.dirname(current);
|
|
36
|
+
}
|
|
37
|
+
return bestRoot || path.resolve(startDir);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Helper to extract candidate project identifiers
|
|
41
|
+
function resolveProjectIdentifiers(projectRoot, homeDir) {
|
|
42
|
+
const ids = new Set();
|
|
43
|
+
|
|
44
|
+
// 1. Check .aos/project.json
|
|
45
|
+
const aosFile = path.join(projectRoot, '.aos', 'project.json');
|
|
46
|
+
if (existsSync(aosFile)) {
|
|
47
|
+
try {
|
|
48
|
+
const cfg = JSON.parse(readFileSync(aosFile, 'utf8'));
|
|
49
|
+
if (cfg?.memb?.projectId) ids.add(cfg.memb.projectId);
|
|
50
|
+
if (cfg?.projectId) ids.add(cfg.projectId);
|
|
51
|
+
if (cfg?.slug) ids.add(cfg.slug);
|
|
52
|
+
if (cfg?.name) ids.add(cfg.name);
|
|
53
|
+
} catch {}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. Check package.json
|
|
57
|
+
const pkgFile = path.join(projectRoot, 'package.json');
|
|
58
|
+
if (existsSync(pkgFile)) {
|
|
59
|
+
try {
|
|
60
|
+
const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'));
|
|
61
|
+
if (typeof pkg?.name === 'string') {
|
|
62
|
+
ids.add(pkg.name);
|
|
63
|
+
ids.add(pkg.name.replace(/^@[^/]+\//, '')); // unscoped name (e.g. @acme/shop-service -> shop-service)
|
|
64
|
+
}
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 3. Basename of project root (excluding home or root)
|
|
69
|
+
if (projectRoot !== homeDir && projectRoot !== '/') {
|
|
70
|
+
ids.add(path.basename(projectRoot));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return Array.from(ids).filter(Boolean);
|
|
74
|
+
}
|
|
75
|
+
|
|
36
76
|
try {
|
|
37
77
|
let inputRaw = '';
|
|
38
78
|
try { inputRaw = readFileSync(0, 'utf8'); } catch { failOpen(); }
|
|
39
79
|
if (!inputRaw || !inputRaw.trim()) failOpen();
|
|
40
80
|
|
|
41
|
-
let eventData;
|
|
81
|
+
let eventData = {};
|
|
42
82
|
try { eventData = JSON.parse(inputRaw); } catch { failOpen(); }
|
|
43
83
|
|
|
44
|
-
const prompt = (eventData.prompt || '').trim();
|
|
45
|
-
const cwd = eventData.cwd || process.cwd();
|
|
46
|
-
const projectName = path.basename(cwd);
|
|
84
|
+
const prompt = (eventData.prompt || eventData.userPrompt || '').trim();
|
|
47
85
|
const home = os.homedir();
|
|
48
86
|
|
|
87
|
+
// Discover candidate directory across harnesses:
|
|
88
|
+
// Claude: eventData.cwd
|
|
89
|
+
// Antigravity: eventData.workspacePaths[0] or eventData.cwd
|
|
90
|
+
// Codex: eventData.cwd
|
|
91
|
+
const rawCandidateDir =
|
|
92
|
+
(Array.isArray(eventData.workspacePaths) && eventData.workspacePaths[0]) ||
|
|
93
|
+
eventData.cwd ||
|
|
94
|
+
process.env.CLAUDE_PROJECT_DIR ||
|
|
95
|
+
process.env.CODEX_PROJECT_DIR ||
|
|
96
|
+
process.cwd();
|
|
97
|
+
|
|
98
|
+
const projectRoot = findProjectRoot(rawCandidateDir, home);
|
|
99
|
+
const candidateProjectIds = resolveProjectIdentifiers(projectRoot, home);
|
|
100
|
+
|
|
101
|
+
// User identification: reconcile active user and bdb_developer baseline
|
|
102
|
+
const activeUser =
|
|
103
|
+
process.env.MEMB_USER_ID ||
|
|
104
|
+
eventData.user_id ||
|
|
105
|
+
eventData.userId ||
|
|
106
|
+
process.env.USER ||
|
|
107
|
+
process.env.LOGNAME ||
|
|
108
|
+
'bdb_developer';
|
|
109
|
+
const candidateUsers = Array.from(new Set([activeUser, 'bdb_developer'])).filter(Boolean);
|
|
110
|
+
|
|
49
111
|
const contextItems = [];
|
|
50
112
|
|
|
51
113
|
// 1. Standing facts, if the user keeps any.
|
|
52
114
|
const personaFile = path.join(home, '.MemBDB', 'ambient-persona.txt');
|
|
53
115
|
if (existsSync(personaFile)) {
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
116
|
+
try {
|
|
117
|
+
for (const line of readFileSync(personaFile, 'utf8').split('\n')) {
|
|
118
|
+
const t = line.trim();
|
|
119
|
+
if (t && !t.startsWith('#')) contextItems.push(`- ${t}`);
|
|
120
|
+
}
|
|
121
|
+
} catch {}
|
|
58
122
|
}
|
|
59
123
|
|
|
60
124
|
const dbPath = path.join(home, '.MemBDB', 'memb.db');
|
|
@@ -68,20 +132,43 @@ try {
|
|
|
68
132
|
return (p.memory || p.data || '').trim().replace(/\s+/g, ' ');
|
|
69
133
|
};
|
|
70
134
|
|
|
71
|
-
// 2.
|
|
72
|
-
if (
|
|
135
|
+
// 2. Query project-specific memories matching any candidate project ID
|
|
136
|
+
if (candidateProjectIds.length > 0) {
|
|
73
137
|
try {
|
|
74
|
-
const
|
|
138
|
+
const pPlaceholders = candidateProjectIds.map(() => '?').join(',');
|
|
139
|
+
const uPlaceholders = candidateUsers.map(() => '?').join(',');
|
|
140
|
+
const sql = `
|
|
75
141
|
SELECT payload FROM memb_vectors
|
|
76
142
|
WHERE collection = ?
|
|
77
|
-
AND (
|
|
78
|
-
|
|
143
|
+
AND (
|
|
144
|
+
json_extract(payload, '$.project_id') IN (${pPlaceholders})
|
|
145
|
+
OR json_extract(payload, '$.metadata.project_id') IN (${pPlaceholders})
|
|
146
|
+
OR json_extract(payload, '$.project') IN (${pPlaceholders})
|
|
147
|
+
OR json_extract(payload, '$.metadata.project') IN (${pPlaceholders})
|
|
148
|
+
)
|
|
149
|
+
AND (
|
|
150
|
+
json_extract(payload, '$.user_id') IS NULL
|
|
151
|
+
OR json_extract(payload, '$.user_id') IN (${uPlaceholders})
|
|
152
|
+
OR json_extract(payload, '$.metadata.user_id') IN (${uPlaceholders})
|
|
153
|
+
)
|
|
79
154
|
ORDER BY rowid DESC
|
|
80
|
-
LIMIT
|
|
81
|
-
|
|
155
|
+
LIMIT 5
|
|
156
|
+
`;
|
|
157
|
+
const params = [
|
|
158
|
+
COLLECTION,
|
|
159
|
+
...candidateProjectIds,
|
|
160
|
+
...candidateProjectIds,
|
|
161
|
+
...candidateProjectIds,
|
|
162
|
+
...candidateProjectIds,
|
|
163
|
+
...candidateUsers,
|
|
164
|
+
...candidateUsers,
|
|
165
|
+
];
|
|
166
|
+
const rows = db.prepare(sql).all(...params);
|
|
82
167
|
for (const r of rows) {
|
|
83
168
|
const text = textOf(r.payload);
|
|
84
|
-
if (text.length > 10)
|
|
169
|
+
if (text.length > 10) {
|
|
170
|
+
contextItems.push(`- Project [${candidateProjectIds[0]}]: ${text.slice(0, 180)}`);
|
|
171
|
+
}
|
|
85
172
|
}
|
|
86
173
|
} catch { /* project query is best-effort */ }
|
|
87
174
|
}
|
|
@@ -114,11 +201,19 @@ try {
|
|
|
114
201
|
}
|
|
115
202
|
|
|
116
203
|
if (contextItems.length) {
|
|
204
|
+
const memoryBlock = '[memB Ambient Memory Context]\n' + contextItems.join('\n');
|
|
205
|
+
const hookEvent = eventData.hook_event_name || 'UserPromptSubmit';
|
|
117
206
|
process.stdout.write(JSON.stringify({
|
|
118
207
|
hookSpecificOutput: {
|
|
119
|
-
hookEventName:
|
|
120
|
-
additionalContext:
|
|
208
|
+
hookEventName: hookEvent,
|
|
209
|
+
additionalContext: memoryBlock,
|
|
121
210
|
},
|
|
211
|
+
injectSteps: [
|
|
212
|
+
{
|
|
213
|
+
ephemeralMessage: memoryBlock,
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
systemMessage: memoryBlock,
|
|
122
217
|
}) + '\n');
|
|
123
218
|
}
|
|
124
219
|
process.exit(0);
|
|
@@ -69,6 +69,298 @@ export const meta = {
|
|
|
69
69
|
'Dispatcher-mediated build pipeline: Architect -> TechLead -> {UI_UX, Engineering, Media_EventTech} -> Reviewer -> Shipping, per .agents/graph.md. Agents never invoke each other -- this script decides every next step. Node identities come from .agents/nodes.json, loaded fresh each run.',
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
+
// Polyfilled module access for standalone CLI and hook executions outside Claude runtime
|
|
73
|
+
let fs = typeof process !== 'undefined' && typeof process.getBuiltinModule === 'function'
|
|
74
|
+
? process.getBuiltinModule('node:fs')
|
|
75
|
+
: null;
|
|
76
|
+
let path = typeof process !== 'undefined' && typeof process.getBuiltinModule === 'function'
|
|
77
|
+
? process.getBuiltinModule('node:path')
|
|
78
|
+
: null;
|
|
79
|
+
let os = typeof process !== 'undefined' && typeof process.getBuiltinModule === 'function'
|
|
80
|
+
? process.getBuiltinModule('node:os')
|
|
81
|
+
: null;
|
|
82
|
+
let child_process = typeof process !== 'undefined' && typeof process.getBuiltinModule === 'function'
|
|
83
|
+
? process.getBuiltinModule('node:child_process')
|
|
84
|
+
: null;
|
|
85
|
+
|
|
86
|
+
if (!fs && typeof process !== 'undefined') {
|
|
87
|
+
try {
|
|
88
|
+
fs = await import('node:fs');
|
|
89
|
+
path = await import('node:path');
|
|
90
|
+
os = await import('node:os');
|
|
91
|
+
child_process = await import('node:child_process');
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let inputGoal = null;
|
|
96
|
+
let inputSkills = [];
|
|
97
|
+
let isHookInvocation = false;
|
|
98
|
+
|
|
99
|
+
// 1. Fast-exit & input handling for hook invocations (Antigravity PreInvocation, Codex UserPromptSubmit)
|
|
100
|
+
if (typeof process !== 'undefined' && process.stdin && !process.stdin.isTTY && fs) {
|
|
101
|
+
try {
|
|
102
|
+
let stdinRaw = '';
|
|
103
|
+
try { stdinRaw = fs.readFileSync(0, 'utf8').trim(); } catch {}
|
|
104
|
+
if (stdinRaw) {
|
|
105
|
+
isHookInvocation = true;
|
|
106
|
+
let payload = null;
|
|
107
|
+
try { payload = JSON.parse(stdinRaw); } catch {}
|
|
108
|
+
|
|
109
|
+
if (payload && typeof payload === 'object') {
|
|
110
|
+
let promptText = payload.prompt || payload.userPrompt || '';
|
|
111
|
+
if (!promptText && payload.transcriptPath && fs.existsSync(payload.transcriptPath)) {
|
|
112
|
+
try {
|
|
113
|
+
const lines = fs.readFileSync(payload.transcriptPath, 'utf8').trim().split('\n');
|
|
114
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
115
|
+
try {
|
|
116
|
+
const entry = JSON.parse(lines[i]);
|
|
117
|
+
if (entry.role === 'user' || entry.type === 'human') {
|
|
118
|
+
promptText = entry.content || entry.text || entry.message || '';
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
} catch {}
|
|
122
|
+
}
|
|
123
|
+
} catch {}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const match = (promptText || '').match(/^\/startcycle-graph(?:\s+(.*))?$/is);
|
|
127
|
+
if (!match) {
|
|
128
|
+
// Clean fast-exit when invoked on non-command prompts
|
|
129
|
+
if (process.stdout) {
|
|
130
|
+
process.stdout.write(JSON.stringify({ injectSteps: [] }) + '\n');
|
|
131
|
+
}
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
inputGoal = (match[1] || '').trim();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch {}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. CLI arguments handling for standalone invocations
|
|
142
|
+
if (inputGoal === null && typeof process !== 'undefined' && Array.isArray(process.argv)) {
|
|
143
|
+
const cliArgs = process.argv.slice(2);
|
|
144
|
+
if (cliArgs.length > 0) {
|
|
145
|
+
if (cliArgs.includes('--help') || cliArgs.includes('-h')) {
|
|
146
|
+
console.log('Usage: node startcycle-dispatch.mjs [--skill=<name>] <goal>');
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
const joined = cliArgs.join(' ').trim();
|
|
150
|
+
const match = joined.match(/^\/startcycle-graph(?:\s+(.*))?$/is);
|
|
151
|
+
inputGoal = match ? (match[1] || '').trim() : joined;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 3. Ambient global args (Claude Dynamic Workflows)
|
|
156
|
+
const ambientArgs = typeof args !== 'undefined' ? args : null;
|
|
157
|
+
if (inputGoal === null && ambientArgs) {
|
|
158
|
+
inputGoal = typeof ambientArgs === 'string' ? ambientArgs : (ambientArgs.goal || '');
|
|
159
|
+
if (Array.isArray(ambientArgs.mandatorySkills)) {
|
|
160
|
+
inputSkills = ambientArgs.mandatorySkills;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 4. Standalone runtime shims for pipeline() and agent()
|
|
165
|
+
if (typeof globalThis.pipeline === 'undefined') {
|
|
166
|
+
globalThis.pipeline = async function standalonePipeline(items, fn) {
|
|
167
|
+
return Promise.all(items.map((item) => fn(item)));
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (typeof globalThis.agent === 'undefined') {
|
|
172
|
+
globalThis.agent = async function standaloneAgent(prompt, options = {}) {
|
|
173
|
+
const label = options.label || '';
|
|
174
|
+
|
|
175
|
+
// Deterministic step: load-registry
|
|
176
|
+
if (label === 'load-registry') {
|
|
177
|
+
try {
|
|
178
|
+
const nodesPath = path ? path.resolve('.agents', 'nodes.json') : '.agents/nodes.json';
|
|
179
|
+
if (fs && fs.existsSync(nodesPath)) {
|
|
180
|
+
return JSON.parse(fs.readFileSync(nodesPath, 'utf8'));
|
|
181
|
+
}
|
|
182
|
+
} catch {}
|
|
183
|
+
return { version: 0, nodes: {} };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Deterministic step: validate-mandatory-skills
|
|
187
|
+
if (label === 'validate-mandatory-skills') {
|
|
188
|
+
const found = [];
|
|
189
|
+
const missing = [];
|
|
190
|
+
const suggestions = [];
|
|
191
|
+
|
|
192
|
+
const skillNames = mandatorySkills || [];
|
|
193
|
+
const home = os ? os.homedir() : '';
|
|
194
|
+
const candidateRoots = [
|
|
195
|
+
path ? path.join(home, '.claude', 'skills') : '',
|
|
196
|
+
path ? path.join(home, '.agents', 'skills') : '',
|
|
197
|
+
path ? path.join(home, '.gemini', 'config', 'skills') : '',
|
|
198
|
+
path ? path.join(home, '.codex', 'skills') : '',
|
|
199
|
+
path ? path.join(home, '.cursor', 'skills') : '',
|
|
200
|
+
path ? path.join(home, '.roo', 'skills') : '',
|
|
201
|
+
path ? path.resolve('skills') : 'skills',
|
|
202
|
+
].filter(Boolean);
|
|
203
|
+
|
|
204
|
+
const allInstalledSkills = new Set();
|
|
205
|
+
if (fs) {
|
|
206
|
+
for (const root of candidateRoots) {
|
|
207
|
+
if (fs.existsSync(root)) {
|
|
208
|
+
try {
|
|
209
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
210
|
+
if (entry.isDirectory()) allInstalledSkills.add(entry.name);
|
|
211
|
+
}
|
|
212
|
+
} catch {}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for (const name of skillNames) {
|
|
218
|
+
let isFound = false;
|
|
219
|
+
if (fs) {
|
|
220
|
+
for (const root of candidateRoots) {
|
|
221
|
+
if (fs.existsSync(path.join(root, name, 'SKILL.md'))) {
|
|
222
|
+
isFound = true;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (isFound) {
|
|
228
|
+
found.push(name);
|
|
229
|
+
} else {
|
|
230
|
+
missing.push(name);
|
|
231
|
+
for (const inst of allInstalledSkills) {
|
|
232
|
+
if (inst.includes(name) || name.includes(inst)) {
|
|
233
|
+
if (!suggestions.includes(inst)) suggestions.push(inst);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return { found, missing, suggestions: suggestions.slice(0, 5) };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Deterministic step: mergeStateD
|
|
242
|
+
if (label.startsWith('merge-')) {
|
|
243
|
+
if (!fs) return { merged: 0, skipped: [], needsHuman: false };
|
|
244
|
+
const stateDDir = path ? path.resolve('production_artifacts', 'state.d') : 'production_artifacts/state.d';
|
|
245
|
+
const statePath = path ? path.resolve('production_artifacts', 'state.json') : 'production_artifacts/state.json';
|
|
246
|
+
|
|
247
|
+
if (!fs.existsSync(stateDDir)) {
|
|
248
|
+
return { merged: 0, skipped: [], needsHuman: false };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let state = {};
|
|
252
|
+
try {
|
|
253
|
+
if (fs.existsSync(statePath)) {
|
|
254
|
+
state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
255
|
+
}
|
|
256
|
+
} catch {}
|
|
257
|
+
state.artifacts = state.artifacts || {};
|
|
258
|
+
state.findings = state.findings || [];
|
|
259
|
+
|
|
260
|
+
let mergedCount = 0;
|
|
261
|
+
const skipped = [];
|
|
262
|
+
let needsHuman = false;
|
|
263
|
+
|
|
264
|
+
let fragmentFiles = [];
|
|
265
|
+
try {
|
|
266
|
+
fragmentFiles = fs.readdirSync(stateDDir).filter((f) => f.endsWith('.json'));
|
|
267
|
+
} catch {}
|
|
268
|
+
|
|
269
|
+
for (const file of fragmentFiles) {
|
|
270
|
+
const fPath = path.join(stateDDir, file);
|
|
271
|
+
try {
|
|
272
|
+
const fragment = JSON.parse(fs.readFileSync(fPath, 'utf8'));
|
|
273
|
+
if (!fragment || typeof fragment !== 'object' || !fragment.node) {
|
|
274
|
+
skipped.push(`${file} (missing required node field)`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (fragment.artifacts && typeof fragment.artifacts === 'object') {
|
|
278
|
+
Object.assign(state.artifacts, fragment.artifacts);
|
|
279
|
+
}
|
|
280
|
+
if (Array.isArray(fragment.findings)) {
|
|
281
|
+
for (const f of fragment.findings) {
|
|
282
|
+
const idx = state.findings.findIndex((existing) => existing.id === f.id);
|
|
283
|
+
if (idx >= 0) {
|
|
284
|
+
state.findings[idx] = { ...state.findings[idx], ...f };
|
|
285
|
+
} else {
|
|
286
|
+
state.findings.push(f);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (fragment.needs_human) needsHuman = true;
|
|
291
|
+
mergedCount++;
|
|
292
|
+
fs.unlinkSync(fPath);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
skipped.push(`${file} (${err.message})`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
state.iteration = iteration;
|
|
299
|
+
if (needsHuman) state.needs_human = true;
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
303
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + '\n');
|
|
304
|
+
} catch {}
|
|
305
|
+
|
|
306
|
+
return { merged: mergedCount, skipped, needsHuman: !!state.needs_human };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Deterministic step: escalate
|
|
310
|
+
if (label === 'escalate') {
|
|
311
|
+
if (fs) {
|
|
312
|
+
const statePath = path ? path.resolve('production_artifacts', 'state.json') : 'production_artifacts/state.json';
|
|
313
|
+
try {
|
|
314
|
+
let state = {};
|
|
315
|
+
if (fs.existsSync(statePath)) {
|
|
316
|
+
state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
317
|
+
}
|
|
318
|
+
state.phase = 'escalated';
|
|
319
|
+
state.needs_human = true;
|
|
320
|
+
state.iteration = iteration;
|
|
321
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
322
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + '\n');
|
|
323
|
+
} catch {}
|
|
324
|
+
}
|
|
325
|
+
return { phase: 'escalated' };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// LLM Agent Steps: runner adapter / graceful execution
|
|
329
|
+
if (child_process) {
|
|
330
|
+
try {
|
|
331
|
+
const agyRes = child_process.spawnSync('agy', ['--print', prompt, '--output-format', 'json', '--print-timeout', '30s'], {
|
|
332
|
+
encoding: 'utf8',
|
|
333
|
+
timeout: 35000,
|
|
334
|
+
});
|
|
335
|
+
if (agyRes.status === 0 && agyRes.stdout) {
|
|
336
|
+
const jsonMatch = agyRes.stdout.match(/\{[\s\S]*\}/);
|
|
337
|
+
if (jsonMatch) {
|
|
338
|
+
return JSON.parse(jsonMatch[0]);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} catch {}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (label.startsWith('architect')) {
|
|
345
|
+
return { planPath: 'production_artifacts/00_execution_plan.md', needsMedia: false };
|
|
346
|
+
}
|
|
347
|
+
if (label.startsWith('techlead')) {
|
|
348
|
+
return { approved: true, reason: 'Approved capability map' };
|
|
349
|
+
}
|
|
350
|
+
if (label.startsWith('reviewer')) {
|
|
351
|
+
return { findings: [], blockingCount: 0 };
|
|
352
|
+
}
|
|
353
|
+
if (label.startsWith('shipping')) {
|
|
354
|
+
return {
|
|
355
|
+
gate: { lint: 'pass', typecheck: 'pass', tests: 'pass', a11y: 'pass', seo: 'pass' },
|
|
356
|
+
blockingNodes: [],
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return { needsHuman: false };
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
72
364
|
const MAX_ITERATIONS = 3;
|
|
73
365
|
|
|
74
366
|
// One shared counter, matching .agents/state.schema.json's single `iteration`
|
|
@@ -298,6 +590,7 @@ const REGISTRY_SCHEMA = {
|
|
|
298
590
|
|
|
299
591
|
const REQUIRED_NODE_IDS = ['architect', 'techlead', 'ui_ux', 'engineering', 'media_eventtech', 'reviewer', 'shipping'];
|
|
300
592
|
|
|
593
|
+
export async function runWorkflow() {
|
|
301
594
|
try {
|
|
302
595
|
const registryResult = await agent(
|
|
303
596
|
'Read the file .agents/nodes.json (repository root) and return its exact contents, parsed as JSON, matching ' +
|
|
@@ -321,9 +614,10 @@ const techleadNode = { id: 'techlead', ...registryNodes.techlead };
|
|
|
321
614
|
const reviewerNode = { id: 'reviewer', ...registryNodes.reviewer };
|
|
322
615
|
const shippingNode = { id: 'shipping', ...registryNodes.shipping };
|
|
323
616
|
|
|
324
|
-
const
|
|
617
|
+
const resolvedArgs = typeof args !== 'undefined' ? args : null;
|
|
618
|
+
const rawGoal = inputGoal || (typeof resolvedArgs === 'string' ? resolvedArgs : resolvedArgs?.goal);
|
|
325
619
|
if (!rawGoal) {
|
|
326
|
-
return escalate(
|
|
620
|
+
return await escalate(
|
|
327
621
|
'startcycle-graph needs a goal, e.g. "Run /startcycle-graph on: add OAuth login with Google" -- nothing was invoked.'
|
|
328
622
|
);
|
|
329
623
|
}
|
|
@@ -375,7 +669,7 @@ if (!goal) {
|
|
|
375
669
|
}
|
|
376
670
|
// Object-form args may also carry a structured list directly, for a future
|
|
377
671
|
// caller that never goes through the string-flag convention at all.
|
|
378
|
-
const skillsFromArgs = Array.isArray(
|
|
672
|
+
const skillsFromArgs = Array.isArray(resolvedArgs?.mandatorySkills) ? resolvedArgs.mandatorySkills : inputSkills;
|
|
379
673
|
const mandatorySkillNames = [...new Set([...skillsFromFlags, ...skillsFromArgs])];
|
|
380
674
|
|
|
381
675
|
// Validate before anything else runs -- same "never silently fall back or
|
|
@@ -804,3 +1098,24 @@ return {
|
|
|
804
1098
|
return { phase: 'escalated', reason: error.message || String(error), needs_human: true };
|
|
805
1099
|
}
|
|
806
1100
|
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// Execution handling: when invoked standalone or via hook, execute runWorkflow() and handle exit
|
|
1104
|
+
const isClaudeWorkflow = typeof globalThis.agent === 'function' && typeof args !== 'undefined' && !isHookInvocation;
|
|
1105
|
+
const result = await runWorkflow();
|
|
1106
|
+
|
|
1107
|
+
if (!isClaudeWorkflow && typeof process !== 'undefined' && process.exit) {
|
|
1108
|
+
if (isHookInvocation && process.stdout) {
|
|
1109
|
+
const msg = `[startcycle-graph] Phase: ${result?.phase || 'done'}. ${result?.reason || ''}`;
|
|
1110
|
+
process.stdout.write(JSON.stringify({
|
|
1111
|
+
injectSteps: [{ ephemeralMessage: msg }],
|
|
1112
|
+
systemMessage: msg,
|
|
1113
|
+
result,
|
|
1114
|
+
}) + '\n');
|
|
1115
|
+
} else if (result && process.stdout) {
|
|
1116
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1117
|
+
}
|
|
1118
|
+
process.exit(0);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
export default result;
|