multi-agents-cli 1.1.38 → 1.1.39
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/core/templates/CLAUDE.md +3 -2
- package/core/workflow/agent.js +2 -2
- package/core/workflow/run.js +3 -0
- package/core/workflow/sync.js +313 -0
- package/init.js +1 -0
- package/package.json +1 -1
package/core/templates/CLAUDE.md
CHANGED
|
@@ -142,7 +142,8 @@ the agent must:
|
|
|
142
142
|
|
|
143
143
|
1. Resolve the project root (OS-agnostic, works on Mac/Linux/Windows):
|
|
144
144
|
```bash
|
|
145
|
-
git rev-parse --
|
|
145
|
+
git rev-parse --path-format=absolute --git-common-dir
|
|
146
|
+
# then take the parent directory of that path - this is PROJECT_ROOT
|
|
146
147
|
```
|
|
147
148
|
Store this as PROJECT_ROOT. All subsequent paths (`.scaffold/`, `BUILD_STATE.md`, `CONTRACTS.md`, etc.) resolve from PROJECT_ROOT, never from the worktree directory.
|
|
148
149
|
2. Read `BUILD_STATE.md` at PROJECT_ROOT - understand what has been built
|
|
@@ -269,7 +270,7 @@ You are operating inside a git worktree — NOT the main repo root.
|
|
|
269
270
|
Before editing ANY file, verify you are in your own worktree:
|
|
270
271
|
|
|
271
272
|
```bash
|
|
272
|
-
git rev-parse --show-toplevel # this is your root
|
|
273
|
+
git rev-parse --show-toplevel # this is your own worktree root - stay within it
|
|
273
274
|
```
|
|
274
275
|
|
|
275
276
|
Never edit files outside your worktree path. If BUILD_STATE.md or
|
package/core/workflow/agent.js
CHANGED
|
@@ -1406,7 +1406,7 @@ Mark each step complete. Only proceed to the task below when all are checked.
|
|
|
1406
1406
|
fs.appendFileSync(beBuildStatePath, beLogEntry, 'utf8');
|
|
1407
1407
|
try {
|
|
1408
1408
|
execSync('git add BUILD_STATE.md', { cwd: ROOT, stdio: 'pipe' });
|
|
1409
|
-
execSync(`git commit --no-verify -m "build: INIT task started on backend [${beBranchName}]"`, { cwd: ROOT, stdio: 'pipe' });
|
|
1409
|
+
execSync(`git commit --no-gpg-sign --no-verify -m "build: INIT task started on backend [${beBranchName}]"`, { cwd: ROOT, stdio: 'pipe' });
|
|
1410
1410
|
console.log(` ${green('✓')} BUILD_STATE.md updated`);
|
|
1411
1411
|
} catch {}
|
|
1412
1412
|
}
|
|
@@ -1696,7 +1696,7 @@ ${excludedUrls}
|
|
|
1696
1696
|
|
|
1697
1697
|
try {
|
|
1698
1698
|
execSync('git add BUILD_STATE.md', { cwd: ROOT, stdio: 'pipe' });
|
|
1699
|
-
execSync(`git commit --no-verify -m "build: ${agent} task started on ${project} [${branchName}]"`, { cwd: ROOT, stdio: 'pipe' });
|
|
1699
|
+
execSync(`git commit --no-gpg-sign --no-verify -m "build: ${agent} task started on ${project} [${branchName}]"`, { cwd: ROOT, stdio: 'pipe' });
|
|
1700
1700
|
console.log(` ${green('✓')} BUILD_STATE.md committed to main`);
|
|
1701
1701
|
} catch (err) {
|
|
1702
1702
|
console.log(` ${yellow('!')} Could not commit BUILD_STATE.md - commit manually if needed`);
|
package/core/workflow/run.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const { execSync, spawn } = require('child_process');
|
|
12
|
+
const { sync } = require('./sync');
|
|
12
13
|
|
|
13
14
|
const ROOT = path.join(__dirname, '..');
|
|
14
15
|
const WORKFLOW_DIR = __dirname;
|
|
@@ -70,6 +71,8 @@ const findCliRoot = () => {
|
|
|
70
71
|
};
|
|
71
72
|
|
|
72
73
|
const main = async () => {
|
|
74
|
+
await sync({ mode: 'auto' });
|
|
75
|
+
|
|
73
76
|
const stamped = getStampedVersion();
|
|
74
77
|
const installed = getCurrentVersion();
|
|
75
78
|
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* sync.js State reconciler.
|
|
6
|
+
*
|
|
7
|
+
* Ground truth is git. Reconciles .scaffold/.tracking.json and BUILD_STATE.md
|
|
8
|
+
* against actual worktrees, branches and merge status. Runs automatically from
|
|
9
|
+
* run.js before every agent launch, and standalone via npm run sync.
|
|
10
|
+
*
|
|
11
|
+
* Design note: status field is read agnostically. Any non null status that is
|
|
12
|
+
* not COMPLETED or PENDING counts as an in flight (active) slot, so the checks
|
|
13
|
+
* fire whether agent.js writes ACTIVE or IN PROGRESS.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { execSync } = require('child_process');
|
|
19
|
+
|
|
20
|
+
const ROOT = path.join(__dirname, '..');
|
|
21
|
+
const TRACKING = path.join(ROOT, '.scaffold', '.tracking.json');
|
|
22
|
+
const BUILD = path.join(ROOT, 'BUILD_STATE.md');
|
|
23
|
+
|
|
24
|
+
// ANSI
|
|
25
|
+
const c = { reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', green: '\x1b[32m', yellow: '\x1b[33m', red: '\x1b[31m' };
|
|
26
|
+
const dim = (s) => `${c.dim}${s}${c.reset}`;
|
|
27
|
+
const green = (s) => `${c.green}${s}${c.reset}`;
|
|
28
|
+
const yellow = (s) => `${c.yellow}${s}${c.reset}`;
|
|
29
|
+
const red = (s) => `${c.red}${s}${c.reset}`;
|
|
30
|
+
const bold = (s) => `${c.bold}${s}${c.reset}`;
|
|
31
|
+
|
|
32
|
+
const gitRead = (cmd) => {
|
|
33
|
+
try { return execSync(`git ${cmd}`, { cwd: ROOT, stdio: 'pipe', encoding: 'utf8' }).trim(); }
|
|
34
|
+
catch { return null; }
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const getWorktrees = () => {
|
|
38
|
+
const out = gitRead('worktree list --porcelain');
|
|
39
|
+
if (!out) return [];
|
|
40
|
+
const wts = [];
|
|
41
|
+
for (const block of out.split('\n\n')) {
|
|
42
|
+
const lines = block.split('\n');
|
|
43
|
+
const p = lines.find(l => l.startsWith('worktree '));
|
|
44
|
+
const b = lines.find(l => l.startsWith('branch '));
|
|
45
|
+
if (p && b) {
|
|
46
|
+
wts.push({
|
|
47
|
+
path: p.replace('worktree ', '').trim(),
|
|
48
|
+
branch: b.replace('branch refs/heads/', '').trim(),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return wts;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const getLocalBranches = () => {
|
|
56
|
+
const out = gitRead('branch --format=%(refname:short)');
|
|
57
|
+
return out ? out.split('\n').map(s => s.trim()).filter(Boolean) : [];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const getMergedBranches = () => {
|
|
61
|
+
const out = gitRead('branch --merged main --format=%(refname:short)');
|
|
62
|
+
return out ? out.split('\n').map(s => s.trim()).filter(Boolean) : [];
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const worktreeHealthy = (wtPath) => {
|
|
66
|
+
if (!wtPath || !fs.existsSync(wtPath)) return false;
|
|
67
|
+
return fs.existsSync(path.join(wtPath, '.git'));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const loadTracking = () => {
|
|
71
|
+
try { return JSON.parse(fs.readFileSync(TRACKING, 'utf8')); } catch { return null; }
|
|
72
|
+
};
|
|
73
|
+
const saveTracking = (t) => fs.writeFileSync(TRACKING, JSON.stringify(t, null, 2) + '\n', 'utf8');
|
|
74
|
+
const emptySlot = () => ({ branch: null, timestamp: null, launchedAt: null, status: null, missingCount: 0, worktreePath: null });
|
|
75
|
+
|
|
76
|
+
const eachSlot = (t, fn) => {
|
|
77
|
+
for (const scope of Object.keys(t)) {
|
|
78
|
+
for (const agent of Object.keys(t[scope])) {
|
|
79
|
+
fn(scope, agent, t[scope][agent]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const TERMINAL = new Set(['COMPLETED']);
|
|
85
|
+
const PARKED = new Set(['PENDING']);
|
|
86
|
+
const isActive = (slot) => slot.status != null && !TERMINAL.has(slot.status) && !PARKED.has(slot.status);
|
|
87
|
+
|
|
88
|
+
const SECTION = { client: '## Client State', backend: '## Backend State', shared: '## Shared' };
|
|
89
|
+
|
|
90
|
+
const flipCheckbox = (content, scope, agent) => {
|
|
91
|
+
const header = SECTION[scope];
|
|
92
|
+
if (!header) return content;
|
|
93
|
+
const start = content.indexOf(header);
|
|
94
|
+
if (start === -1) return content;
|
|
95
|
+
const rest = content.slice(start + header.length);
|
|
96
|
+
const nextH = rest.indexOf('\n## ');
|
|
97
|
+
const blockEnd = nextH === -1 ? content.length : start + header.length + nextH;
|
|
98
|
+
const before = content.slice(0, start);
|
|
99
|
+
const block = content.slice(start, blockEnd);
|
|
100
|
+
const after = content.slice(blockEnd);
|
|
101
|
+
const patched = block.replace(`- [ ] ${agent} `, `- [x] ${agent} `);
|
|
102
|
+
return before + patched + after;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const ensureCompletedRow = (content, scope, agent, branch) => {
|
|
106
|
+
let patched = false, appended = false;
|
|
107
|
+
const esc = branch.replace(/\//g, '\\/');
|
|
108
|
+
|
|
109
|
+
const inProg = new RegExp(`(\\| [^|]+ \\| [^|]+ \\| [^|]+ \\| [^|]+ \\|) IN PROGRESS (\\| ${esc} \\|)`);
|
|
110
|
+
if (inProg.test(content)) {
|
|
111
|
+
content = content.replace(inProg, `$1 COMPLETED $2`);
|
|
112
|
+
patched = true;
|
|
113
|
+
} else if (!content.includes(branch)) {
|
|
114
|
+
const date = new Date().toISOString().split('T')[0];
|
|
115
|
+
const row = `| ${date} | ${agent} | ${scope} | reconciled by sync | COMPLETED | ${branch} |`;
|
|
116
|
+
const lines = content.split('\n');
|
|
117
|
+
let lastRow = -1;
|
|
118
|
+
for (let i = 0; i < lines.length; i++) if (/^\|/.test(lines[i])) lastRow = i;
|
|
119
|
+
if (lastRow !== -1) { lines.splice(lastRow + 1, 0, row); content = lines.join('\n'); appended = true; }
|
|
120
|
+
}
|
|
121
|
+
content = flipCheckbox(content, scope, agent);
|
|
122
|
+
return { content, patched, appended };
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const findTaskMd = (wtPath, scope) => {
|
|
126
|
+
const candidates = [
|
|
127
|
+
path.join(wtPath, 'TASK.md'),
|
|
128
|
+
path.join(wtPath, scope, 'TASK.md'),
|
|
129
|
+
path.join(wtPath, 'client', 'TASK.md'),
|
|
130
|
+
path.join(wtPath, 'backend', 'TASK.md'),
|
|
131
|
+
];
|
|
132
|
+
return candidates.find(p => fs.existsSync(p)) || null;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const writeSyncNotice = (taskPath, actions, stamp) => {
|
|
136
|
+
const block = [
|
|
137
|
+
``,
|
|
138
|
+
`<!-- SYNC NOTICE ${stamp} -->`,
|
|
139
|
+
`## Sync notice`,
|
|
140
|
+
`sync.js reconciled shared state at this session start:`,
|
|
141
|
+
...actions.map(a => `- ${a}`),
|
|
142
|
+
`Main BUILD_STATE.md and .tracking.json now reflect the above.`,
|
|
143
|
+
`Re read \`git show main:BUILD_STATE.md\` before continuing.`,
|
|
144
|
+
`<!-- END SYNC NOTICE -->`,
|
|
145
|
+
``,
|
|
146
|
+
].join('\n');
|
|
147
|
+
fs.appendFileSync(taskPath, block, 'utf8');
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const arrowSelect = async (message, choices) => {
|
|
151
|
+
try {
|
|
152
|
+
const prompts = require('prompts');
|
|
153
|
+
const res = await prompts({
|
|
154
|
+
type: 'select', name: 'v', message,
|
|
155
|
+
choices: choices.map((c2, i) => ({ title: c2, value: i })),
|
|
156
|
+
});
|
|
157
|
+
return typeof res.v === 'number' ? res.v : 0;
|
|
158
|
+
} catch {
|
|
159
|
+
console.log(`\n ${message}`);
|
|
160
|
+
choices.forEach((c2, i) => console.log(` ${dim(`${i + 1}.`)} ${c2}`));
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
async function sync(opts = {}) {
|
|
166
|
+
const mode = opts.mode || 'standalone';
|
|
167
|
+
const tracking = loadTracking();
|
|
168
|
+
if (!tracking) { if (mode === 'standalone') console.log(dim(' sync: no .tracking.json, nothing to reconcile')); return; }
|
|
169
|
+
|
|
170
|
+
const localBranches = getLocalBranches();
|
|
171
|
+
const mergedBranches = getMergedBranches();
|
|
172
|
+
const worktrees = getWorktrees();
|
|
173
|
+
|
|
174
|
+
const actions = [];
|
|
175
|
+
const decisions = [];
|
|
176
|
+
const orphans = [];
|
|
177
|
+
let changed = false;
|
|
178
|
+
let buildChanged = false;
|
|
179
|
+
const inFlightPatched = new Set();
|
|
180
|
+
|
|
181
|
+
// Check 1 branch existence
|
|
182
|
+
eachSlot(tracking, (scope, agent, slot) => {
|
|
183
|
+
if (!slot.branch) return;
|
|
184
|
+
if (!localBranches.includes(slot.branch) && isActive(slot)) {
|
|
185
|
+
slot.status = 'MISSING'; changed = true;
|
|
186
|
+
actions.push(`${scope}/${agent}: branch ${slot.branch} gone, marked MISSING`);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Check 2 worktree health
|
|
191
|
+
eachSlot(tracking, (scope, agent, slot) => {
|
|
192
|
+
if (!isActive(slot) && slot.status !== 'MISSING') return;
|
|
193
|
+
const branchLive = localBranches.includes(slot.branch);
|
|
194
|
+
const wt = worktrees.find(w => w.branch === slot.branch);
|
|
195
|
+
const pathLive = wt ? worktreeHealthy(wt.path) : worktreeHealthy(slot.worktreePath);
|
|
196
|
+
|
|
197
|
+
if (!pathLive && branchLive && slot.status !== 'MISSING') {
|
|
198
|
+
slot.status = 'MISSING'; changed = true;
|
|
199
|
+
actions.push(`${scope}/${agent}: worktree missing, branch alive, marked MISSING`);
|
|
200
|
+
} else if (!pathLive && !branchLive) {
|
|
201
|
+
Object.assign(slot, emptySlot()); changed = true;
|
|
202
|
+
actions.push(`${scope}/${agent}: worktree and branch both gone, tracking cleaned (ABANDONED)`);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Check 3 merge reflection
|
|
207
|
+
const buildTargets = [];
|
|
208
|
+
eachSlot(tracking, (scope, agent, slot) => {
|
|
209
|
+
if (!slot.branch) return;
|
|
210
|
+
if (!isActive(slot) && slot.status !== 'MISSING') return;
|
|
211
|
+
if (mergedBranches.includes(slot.branch)) {
|
|
212
|
+
const wt = worktrees.find(w => w.branch === slot.branch);
|
|
213
|
+
if (wt && worktreeHealthy(wt.path)) orphans.push(`${scope}/${agent} -> ${wt.path}`);
|
|
214
|
+
slot.status = 'COMPLETED';
|
|
215
|
+
if (!slot.completedAt) slot.completedAt = new Date().toISOString();
|
|
216
|
+
slot.worktreePath = null;
|
|
217
|
+
changed = true;
|
|
218
|
+
actions.push(`${scope}/${agent}: branch merged into main, marked COMPLETED`);
|
|
219
|
+
buildTargets.push({ scope, agent, branch: slot.branch });
|
|
220
|
+
inFlightPatched.add(scope);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Check 4 BUILD_STATE vs tracking drift
|
|
225
|
+
eachSlot(tracking, (scope, agent, slot) => {
|
|
226
|
+
if (slot.status !== 'COMPLETED' || !slot.branch) return;
|
|
227
|
+
if (!buildTargets.find(b => b.branch === slot.branch)) buildTargets.push({ scope, agent, branch: slot.branch });
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
if (buildTargets.length && fs.existsSync(BUILD)) {
|
|
231
|
+
let content = fs.readFileSync(BUILD, 'utf8');
|
|
232
|
+
for (const t of buildTargets) {
|
|
233
|
+
const r = ensureCompletedRow(content, t.scope, t.agent, t.branch);
|
|
234
|
+
if (r.content !== content) {
|
|
235
|
+
content = r.content; buildChanged = true;
|
|
236
|
+
if (r.appended) actions.push(`BUILD_STATE: appended COMPLETED row for ${t.scope}/${t.agent}`);
|
|
237
|
+
else if (r.patched) actions.push(`BUILD_STATE: ${t.scope}/${t.agent} IN PROGRESS -> COMPLETED`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (buildChanged) fs.writeFileSync(BUILD, content, 'utf8');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (changed) saveTracking(tracking);
|
|
244
|
+
|
|
245
|
+
// Check 5 absent scaffold (advisory)
|
|
246
|
+
for (const scope of Object.keys(tracking)) {
|
|
247
|
+
if (scope === 'shared') continue;
|
|
248
|
+
const scopeDir = path.join(ROOT, scope);
|
|
249
|
+
if (!fs.existsSync(scopeDir)) continue;
|
|
250
|
+
const slots = Object.values(tracking[scope]);
|
|
251
|
+
const anyCompleted = slots.some(s => s.status === 'COMPLETED');
|
|
252
|
+
const anyActive = slots.some(s => isActive(s));
|
|
253
|
+
if (!anyCompleted && !anyActive) {
|
|
254
|
+
decisions.push(`${scope}: scope folder exists but no agent started run npm run agent to begin`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Agent awareness TASK.md notices
|
|
259
|
+
if (actions.length && inFlightPatched.size) {
|
|
260
|
+
const stamp = new Date().toISOString();
|
|
261
|
+
eachSlot(tracking, (scope, agent, slot) => {
|
|
262
|
+
if (!isActive(slot) || !inFlightPatched.has(scope)) return;
|
|
263
|
+
const wt = worktrees.find(w => w.branch === slot.branch);
|
|
264
|
+
if (!wt || !worktreeHealthy(wt.path)) return;
|
|
265
|
+
const taskPath = findTaskMd(wt.path, scope);
|
|
266
|
+
if (!taskPath) { decisions.push(`${scope}/${agent}: live worktree but no TASK.md found for sync notice`); return; }
|
|
267
|
+
try { writeSyncNotice(taskPath, actions, stamp); actions.push(`TASK.md notice written for in flight ${scope}/${agent}`); }
|
|
268
|
+
catch (e) { decisions.push(`${scope}/${agent}: TASK.md notice write failed ${e.message}`); }
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Check 6 dirty state files -> auto commit
|
|
273
|
+
const dirty = gitRead('status --porcelain') || '';
|
|
274
|
+
const dirtyState = dirty.split('\n')
|
|
275
|
+
.map(l => l.slice(3).trim())
|
|
276
|
+
.filter(f => f === 'BUILD_STATE.md' || f.endsWith('.tracking.json'));
|
|
277
|
+
if (dirtyState.length) {
|
|
278
|
+
try {
|
|
279
|
+
execSync('git add BUILD_STATE.md .scaffold/.tracking.json', { cwd: ROOT, stdio: 'pipe' });
|
|
280
|
+
execSync('git commit --no-gpg-sign --no-verify -m "sync: reconcile state files"', { cwd: ROOT, stdio: 'pipe' });
|
|
281
|
+
actions.push(`committed reconciled state files (${dirtyState.join(', ')})`);
|
|
282
|
+
} catch (e) {
|
|
283
|
+
decisions.push(`state files dirty but auto commit failed ${e.message}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const clean = !actions.length && !decisions.length && !orphans.length;
|
|
288
|
+
if (clean) { if (mode === 'standalone') console.log(green(' \u2713 state in sync')); return; }
|
|
289
|
+
|
|
290
|
+
console.log(`\n${bold(' Sync report')}`);
|
|
291
|
+
if (actions.length) {
|
|
292
|
+
console.log(green(' auto patched:'));
|
|
293
|
+
actions.forEach(a => console.log(dim(' \u00b7 ') + a));
|
|
294
|
+
}
|
|
295
|
+
if (orphans.length) {
|
|
296
|
+
console.log(yellow(' orphaned worktrees (merged, safe to remove):'));
|
|
297
|
+
orphans.forEach(o => console.log(dim(' \u00b7 ') + o + dim(' git worktree remove <path>')));
|
|
298
|
+
}
|
|
299
|
+
if (decisions.length) {
|
|
300
|
+
console.log(yellow(' needs your attention:'));
|
|
301
|
+
decisions.forEach(d => console.log(dim(' \u00b7 ') + d));
|
|
302
|
+
if (mode === 'standalone') {
|
|
303
|
+
await arrowSelect('How to proceed?', ['Acknowledge and continue', 'Abort']);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
console.log('');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
module.exports = { sync };
|
|
310
|
+
|
|
311
|
+
if (require.main === module) {
|
|
312
|
+
sync({ mode: 'standalone' }).catch(err => { console.error(red(' sync error: ') + err.message); process.exit(1); });
|
|
313
|
+
}
|
package/init.js
CHANGED
|
@@ -679,6 +679,7 @@ Before starting any task, verify:
|
|
|
679
679
|
restart: 'node .workflow/restart.js',
|
|
680
680
|
reset: 'node .workflow/reset.js',
|
|
681
681
|
complete: 'node .workflow/complete.js',
|
|
682
|
+
sync: 'node .workflow/sync.js',
|
|
682
683
|
},
|
|
683
684
|
};
|
|
684
685
|
fs.writeFileSync(path.join(ROOT, 'package.json'), JSON.stringify(userPackage, null, 2), 'utf8');
|
package/package.json
CHANGED