atris 3.35.0 → 3.37.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.
- package/AGENTS.md +37 -0
- package/README.md +5 -3
- package/atris/GETTING_STARTED.md +1 -1
- package/atris/atris.md +3 -0
- package/atris/policies/day-loop-voice.md +102 -0
- package/atris/policies/outbound-artifact-gate.md +2 -0
- package/atris/skills/design/SKILL.md +56 -32
- package/atris/skills/endgame/SKILL.md +12 -6
- package/atris/skills/engines/SKILL.md +22 -4
- package/atris/skills/fable-method/SKILL.md +66 -0
- package/atris/skills/improve/SKILL.md +65 -45
- package/atris/skills/youtube/SKILL.md +10 -1
- package/atris.md +2 -0
- package/ax +147 -19
- package/bin/atris.js +576 -267
- package/commands/activate.js +194 -88
- package/commands/agents.js +166 -0
- package/commands/autoland.js +459 -107
- package/commands/autopilot-front.js +20 -2
- package/commands/autopilot.js +118 -2
- package/commands/avail.js +407 -0
- package/commands/bench.js +188 -0
- package/commands/brain.js +3 -0
- package/commands/brief.js +651 -0
- package/commands/business-sync.js +192 -6
- package/commands/clean.js +50 -24
- package/commands/close.js +1083 -0
- package/commands/cloud.js +245 -0
- package/commands/compile.js +292 -1
- package/commands/computer.js +150 -3
- package/commands/dream.js +365 -0
- package/commands/drill.js +371 -0
- package/commands/engine.js +993 -32
- package/commands/experiments.js +28 -0
- package/commands/feedback.js +34 -12
- package/commands/fleet-report.js +206 -0
- package/commands/gm.js +23 -0
- package/commands/goal.js +247 -0
- package/commands/improve.js +642 -26
- package/commands/init.js +72 -44
- package/commands/interview.js +67 -1
- package/commands/land.js +152 -52
- package/commands/lifecycle.js +39 -3
- package/commands/log.js +84 -1
- package/commands/loops.js +220 -16
- package/commands/meet.js +220 -0
- package/commands/member.js +511 -34
- package/commands/mission.js +3144 -338
- package/commands/next.js +137 -0
- package/commands/now.js +220 -25
- package/commands/one-lap.js +776 -0
- package/commands/orb.js +314 -0
- package/commands/pack-craft.js +179 -0
- package/commands/pack.js +823 -0
- package/commands/play.js +3 -2
- package/commands/probe.js +30 -3
- package/commands/pulse.js +241 -46
- package/commands/push.js +260 -82
- package/commands/rainmaker.js +49 -0
- package/commands/report.js +415 -0
- package/commands/scout.js +147 -0
- package/commands/search.js +363 -0
- package/commands/skill.js +47 -3
- package/commands/slop.js +50 -2
- package/commands/soul.js +1 -1
- package/commands/stream.js +861 -0
- package/commands/study.js +693 -0
- package/commands/sync.js +67 -54
- package/commands/task.js +1346 -117
- package/commands/team.js +73 -0
- package/commands/verify.js +96 -0
- package/commands/watch.js +303 -0
- package/commands/wish.js +500 -0
- package/commands/workflow.js +11 -5
- package/commands/worktree.js +234 -13
- package/commands/xp.js +29 -11
- package/lib/auto-accept-certified.js +331 -34
- package/lib/autoland.js +319 -54
- package/lib/ax-auto-lane.js +96 -0
- package/lib/bench/context.js +147 -0
- package/lib/bench/engines.js +141 -0
- package/lib/bench/report.js +140 -0
- package/lib/bench/runner.js +512 -0
- package/lib/brief-ledger.js +350 -0
- package/lib/cloud-mission.js +259 -0
- package/lib/codex-flight.js +154 -0
- package/lib/default-runner.js +45 -0
- package/lib/default-verifier.js +70 -0
- package/lib/engine-registry.js +232 -0
- package/lib/experiments/daily.js +640 -0
- package/lib/fleet.js +2219 -67
- package/lib/improve-vitals-html.js +171 -0
- package/lib/known-commands.js +58 -0
- package/lib/loop-doctor.js +416 -0
- package/lib/member-switches.js +144 -0
- package/lib/mission-room.js +1 -0
- package/lib/mission-root.js +52 -0
- package/lib/next-moves.js +327 -10
- package/lib/one-lap-validator.js +60 -0
- package/lib/orb-context.js +477 -0
- package/lib/orb-scorecard.js +224 -0
- package/lib/policy-lessons.js +52 -1
- package/lib/pulse.js +277 -3
- package/lib/receipt-block.js +168 -0
- package/lib/receipt-evidence.js +65 -4
- package/lib/router-brain.js +352 -0
- package/lib/runner-command.js +10 -0
- package/lib/self-drive.js +258 -0
- package/lib/short-name.js +103 -0
- package/lib/spawn-env.js +18 -0
- package/lib/state-detection.js +56 -1
- package/lib/sync-status.js +59 -0
- package/lib/task-db.js +108 -29
- package/lib/task-proof.js +23 -1
- package/lib/team-presence.js +260 -0
- package/lib/tool-result-encode.js +7 -0
- package/lib/trust-tiers.js +90 -0
- package/lib/usage.js +107 -0
- package/lib/voice-gate.js +163 -0
- package/lib/wish-audit.js +1368 -0
- package/lib/wish-delegate.js +1840 -0
- package/lib/wish-design.js +110 -0
- package/lib/wish-stats.js +183 -0
- package/lib/wish-store.js +354 -0
- package/lib/zip.js +221 -0
- package/package.json +3 -1
- package/templates/loops/atris/loops/LOOPS.md +55 -0
- package/templates/loops/atris/loops/TICK.md +24 -0
- package/templates/loops/atris/loops/feedback.md +22 -0
- package/templates/loops/atris/loops/quality.md +22 -0
- package/templates/loops/atris/wiki/systems/loops.md +41 -0
- package/utils/api.js +5 -1
- package/utils/auth.js +57 -21
package/commands/sync.js
CHANGED
|
@@ -89,12 +89,15 @@ function syncPackageSkills(targetAtrisDir, opts = {}) {
|
|
|
89
89
|
const userSkillsDir = path.join(targetAtrisDir, 'skills');
|
|
90
90
|
const claudeSkillsBaseDir = path.join(path.dirname(targetAtrisDir), '.claude', 'skills');
|
|
91
91
|
const verbose = opts.verbose !== false;
|
|
92
|
+
const dryRun = opts.dryRun === true;
|
|
92
93
|
let updated = 0;
|
|
93
94
|
|
|
94
95
|
if (!fs.existsSync(packageSkillsDir)) return 0;
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
if (!dryRun) {
|
|
98
|
+
ensureRealDirectory(userSkillsDir);
|
|
99
|
+
ensureRealDirectory(claudeSkillsBaseDir);
|
|
100
|
+
}
|
|
98
101
|
|
|
99
102
|
const skillFolders = fs.readdirSync(packageSkillsDir).filter(f => {
|
|
100
103
|
try { return fs.statSync(path.join(packageSkillsDir, f)).isDirectory(); }
|
|
@@ -107,7 +110,7 @@ function syncPackageSkills(targetAtrisDir, opts = {}) {
|
|
|
107
110
|
const symlinkPath = path.join(claudeSkillsBaseDir, skill);
|
|
108
111
|
|
|
109
112
|
const syncRecursive = (src, dest, skillName, basePath = '') => {
|
|
110
|
-
ensureRealDirectory(dest);
|
|
113
|
+
if (!dryRun) ensureRealDirectory(dest);
|
|
111
114
|
for (const entry of fs.readdirSync(src)) {
|
|
112
115
|
const srcPath = path.join(src, entry);
|
|
113
116
|
const destPath = path.join(dest, entry);
|
|
@@ -118,9 +121,11 @@ function syncPackageSkills(targetAtrisDir, opts = {}) {
|
|
|
118
121
|
const srcContent = fs.readFileSync(srcPath, 'utf8');
|
|
119
122
|
const destContent = fs.existsSync(destPath) ? fs.readFileSync(destPath, 'utf8') : '';
|
|
120
123
|
if (srcContent !== destContent) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
if (!dryRun) {
|
|
125
|
+
fs.writeFileSync(destPath, srcContent);
|
|
126
|
+
if (entry.endsWith('.sh')) fs.chmodSync(destPath, 0o755);
|
|
127
|
+
}
|
|
128
|
+
if (verbose) console.log(`${dryRun ? 'Would update' : '✓ Updated'} atris/skills/${skillName}/${relPath}`);
|
|
124
129
|
updated++;
|
|
125
130
|
}
|
|
126
131
|
}
|
|
@@ -131,16 +136,20 @@ function syncPackageSkills(targetAtrisDir, opts = {}) {
|
|
|
131
136
|
|
|
132
137
|
if (!fs.existsSync(symlinkPath)) {
|
|
133
138
|
const relativePath = path.relative(claudeSkillsBaseDir, destSkillDir);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
if (dryRun) {
|
|
140
|
+
if (verbose) console.log(`Would link .claude/skills/${skill}`);
|
|
141
|
+
} else {
|
|
142
|
+
try {
|
|
143
|
+
fs.symlinkSync(relativePath, symlinkPath);
|
|
144
|
+
if (verbose) console.log(`✓ Linked .claude/skills/${skill}`);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
ensureRealDirectory(symlinkPath);
|
|
147
|
+
const skillFile = path.join(destSkillDir, 'SKILL.md');
|
|
148
|
+
if (fs.existsSync(skillFile)) {
|
|
149
|
+
fs.copyFileSync(skillFile, path.join(symlinkPath, 'SKILL.md'));
|
|
150
|
+
}
|
|
151
|
+
if (verbose) console.log(`✓ Copied .claude/skills/${skill} (symlink failed)`);
|
|
142
152
|
}
|
|
143
|
-
if (verbose) console.log(`✓ Copied .claude/skills/${skill} (symlink failed)`);
|
|
144
153
|
}
|
|
145
154
|
}
|
|
146
155
|
}
|
|
@@ -242,7 +251,7 @@ function renderBusinessAgentAdapter(bizMeta = {}, targetRoot = '.') {
|
|
|
242
251
|
'- Check `atris/MAP.md` before broad code or file search.',
|
|
243
252
|
'- Use `atris task` for ownership, notes, proof, and review state.',
|
|
244
253
|
'- Use `atris mission` when work should survive the current chat.',
|
|
245
|
-
'- Put completed agent work in Review with `atris task ready <id> --proof "<receipt>".`',
|
|
254
|
+
'- Put completed agent work in Review with `atris task ready <id> --proof "<receipt>" --result "<day-one PM sentence>".`',
|
|
246
255
|
'- Do not run `atris task accept` or claim XP unless a human approved the proof.',
|
|
247
256
|
'- Do not mix another business into this workspace.',
|
|
248
257
|
'- No external sends, spend, or launches without operator approval.',
|
|
@@ -355,16 +364,13 @@ function syncWorkspaceTemplate(targetRoot, bizMeta, options = {}) {
|
|
|
355
364
|
// customer workspace. Business-starter template ships skill infra (README,
|
|
356
365
|
// folders) but skill files live in atris-cli/atris/skills/ — single source
|
|
357
366
|
// of truth. Any new skill (e.g. AEO) auto-propagates to every customer.
|
|
358
|
-
|
|
359
|
-
if (!dryRun) {
|
|
360
|
-
skillsUpdated = syncPackageSkills(targetAtrisDir, { verbose: false });
|
|
361
|
-
}
|
|
367
|
+
const skillsUpdated = syncPackageSkills(targetAtrisDir, { verbose: false, dryRun });
|
|
362
368
|
|
|
363
369
|
console.log(` Added: ${added}`);
|
|
364
370
|
console.log(` Updated: ${updated} ${force ? '' : '(--force to enable)'}`);
|
|
365
371
|
console.log(` Preserved: ${preserved} (existing customizations kept)`);
|
|
366
372
|
console.log(` Skipped: ${skipped} (already match template)`);
|
|
367
|
-
console.log(` Skills: ${skillsUpdated} updated from atris-cli/atris/skills/`);
|
|
373
|
+
console.log(` Skills: ${skillsUpdated} ${dryRun ? 'would update' : 'updated'} from atris-cli/atris/skills/`);
|
|
368
374
|
console.log('');
|
|
369
375
|
|
|
370
376
|
if (addedList.length > 0) {
|
|
@@ -421,7 +427,9 @@ function syncBusinessCanonical(targetRoot, bizMeta, options = {}) {
|
|
|
421
427
|
});
|
|
422
428
|
}
|
|
423
429
|
|
|
424
|
-
function syncAtris() {
|
|
430
|
+
function syncAtris(options = {}) {
|
|
431
|
+
const dryRun = options.dryRun != null ? options.dryRun : process.argv.includes('--dry-run');
|
|
432
|
+
const force = options.force != null ? options.force : process.argv.includes('--force');
|
|
425
433
|
// Business mode detection: if .atris/business.json exists, use canonical templates
|
|
426
434
|
const bizFile = path.join(process.cwd(), '.atris', 'business.json');
|
|
427
435
|
if (fs.existsSync(bizFile)) {
|
|
@@ -429,6 +437,8 @@ function syncAtris() {
|
|
|
429
437
|
const bizMeta = JSON.parse(fs.readFileSync(bizFile, 'utf8'));
|
|
430
438
|
return syncWorkspaceTemplate(process.cwd(), bizMeta, {
|
|
431
439
|
templateName: bizMeta.workspace_template || 'business',
|
|
440
|
+
dryRun,
|
|
441
|
+
force,
|
|
432
442
|
});
|
|
433
443
|
} catch (e) {
|
|
434
444
|
console.error(`✗ Failed to read .atris/business.json: ${e.message}`);
|
|
@@ -452,7 +462,7 @@ function syncAtris() {
|
|
|
452
462
|
console.log('📦 Migrating agent_team/ → team/ (v2.1.0 update)');
|
|
453
463
|
|
|
454
464
|
// Create team/ if it doesn't exist
|
|
455
|
-
if (!fs.existsSync(teamDir)) {
|
|
465
|
+
if (!dryRun && !fs.existsSync(teamDir)) {
|
|
456
466
|
fs.mkdirSync(teamDir, { recursive: true });
|
|
457
467
|
}
|
|
458
468
|
|
|
@@ -465,27 +475,27 @@ function syncAtris() {
|
|
|
465
475
|
// Only copy if destination doesn't exist (preserve any customizations)
|
|
466
476
|
if (!fs.existsSync(destPath)) {
|
|
467
477
|
if (fs.statSync(srcPath).isFile()) {
|
|
468
|
-
fs.copyFileSync(srcPath, destPath);
|
|
469
|
-
console.log(` ✓ Migrated ${file}`);
|
|
478
|
+
if (!dryRun) fs.copyFileSync(srcPath, destPath);
|
|
479
|
+
console.log(` ${dryRun ? 'Would migrate' : '✓ Migrated'} ${file}`);
|
|
470
480
|
}
|
|
471
481
|
}
|
|
472
482
|
}
|
|
473
483
|
|
|
474
484
|
// Remove old agent_team/ folder
|
|
475
|
-
fs.rmSync(legacyAgentTeamDir, { recursive: true, force: true });
|
|
476
|
-
console.log('
|
|
485
|
+
if (!dryRun) fs.rmSync(legacyAgentTeamDir, { recursive: true, force: true });
|
|
486
|
+
console.log(` ${dryRun ? 'Would remove' : '✓ Removed'} old agent_team/ folder`);
|
|
477
487
|
console.log('');
|
|
478
488
|
}
|
|
479
489
|
|
|
480
490
|
if (!fs.existsSync(teamDir)) {
|
|
481
|
-
fs.mkdirSync(teamDir, { recursive: true });
|
|
491
|
+
if (!dryRun) fs.mkdirSync(teamDir, { recursive: true });
|
|
482
492
|
}
|
|
483
493
|
|
|
484
494
|
// Ensure policies folder exists
|
|
485
495
|
const policiesDir = path.join(targetDir, 'policies');
|
|
486
496
|
if (!fs.existsSync(policiesDir)) {
|
|
487
|
-
fs.mkdirSync(policiesDir, { recursive: true });
|
|
488
|
-
console.log('✓ Created atris/policies/ folder
|
|
497
|
+
if (!dryRun) fs.mkdirSync(policiesDir, { recursive: true });
|
|
498
|
+
console.log(`${dryRun ? 'Would create' : '✓ Created'} atris/policies/ folder`);
|
|
489
499
|
}
|
|
490
500
|
|
|
491
501
|
const filesToSync = [
|
|
@@ -522,9 +532,11 @@ function syncAtris() {
|
|
|
522
532
|
return;
|
|
523
533
|
}
|
|
524
534
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
535
|
+
if (!dryRun) {
|
|
536
|
+
fs.mkdirSync(path.dirname(targetFile), { recursive: true });
|
|
537
|
+
fs.copyFileSync(sourceFile, targetFile);
|
|
538
|
+
}
|
|
539
|
+
console.log(`${dryRun ? 'Would update' : '✓ Updated'} ${target}`);
|
|
528
540
|
updated++;
|
|
529
541
|
});
|
|
530
542
|
|
|
@@ -532,12 +544,13 @@ function syncAtris() {
|
|
|
532
544
|
const todoFile = path.join(targetDir, 'TODO.md');
|
|
533
545
|
const legacyTaskFile = path.join(targetDir, 'TASK_CONTEXTS.md');
|
|
534
546
|
if (!fs.existsSync(todoFile) && fs.existsSync(legacyTaskFile)) {
|
|
535
|
-
fs.renameSync(legacyTaskFile, todoFile);
|
|
536
|
-
console.log('✓ Migrated TASK_CONTEXTS.md to TODO.md
|
|
547
|
+
if (!dryRun) fs.renameSync(legacyTaskFile, todoFile);
|
|
548
|
+
console.log(`${dryRun ? 'Would migrate' : '✓ Migrated'} TASK_CONTEXTS.md to TODO.md`);
|
|
549
|
+
updated++;
|
|
537
550
|
}
|
|
538
551
|
|
|
539
552
|
// Sync all skills from package to user's project via shared helper.
|
|
540
|
-
updated += syncPackageSkills(targetDir, { verbose: true });
|
|
553
|
+
updated += syncPackageSkills(targetDir, { verbose: true, dryRun });
|
|
541
554
|
|
|
542
555
|
// Update .claude/skills/atris/SKILL.md (legacy - now handled above, keeping for compatibility)
|
|
543
556
|
const claudeSkillsDir = path.join(process.cwd(), '.claude', 'skills', 'atris');
|
|
@@ -572,13 +585,13 @@ Key behaviors:
|
|
|
572
585
|
- Use \`atris task\` for claims, proof, ready, and accept
|
|
573
586
|
- Treat TODO.md as a rendered view; regenerate it instead of hand-editing tasks`;
|
|
574
587
|
|
|
575
|
-
if (!fs.existsSync(claudeSkillsDir)) {
|
|
588
|
+
if (!dryRun && !fs.existsSync(claudeSkillsDir)) {
|
|
576
589
|
fs.mkdirSync(claudeSkillsDir, { recursive: true });
|
|
577
590
|
}
|
|
578
591
|
const currentSkill = fs.existsSync(claudeSkillFile) ? fs.readFileSync(claudeSkillFile, 'utf8') : '';
|
|
579
592
|
if (currentSkill !== skillContent) {
|
|
580
|
-
fs.writeFileSync(claudeSkillFile, skillContent);
|
|
581
|
-
console.log('✓ Updated .claude/skills/atris/SKILL.md
|
|
593
|
+
if (!dryRun) fs.writeFileSync(claudeSkillFile, skillContent);
|
|
594
|
+
console.log(`${dryRun ? 'Would update' : '✓ Updated'} .claude/skills/atris/SKILL.md`);
|
|
582
595
|
updated++;
|
|
583
596
|
}
|
|
584
597
|
|
|
@@ -599,8 +612,8 @@ Key behaviors:
|
|
|
599
612
|
]
|
|
600
613
|
}
|
|
601
614
|
};
|
|
602
|
-
fs.writeFileSync(claudeSettingsFile, JSON.stringify(claudeSettings, null, 2));
|
|
603
|
-
console.log('✓ Created .claude/settings.json (SessionStart hook)
|
|
615
|
+
if (!dryRun) fs.writeFileSync(claudeSettingsFile, JSON.stringify(claudeSettings, null, 2));
|
|
616
|
+
console.log(`${dryRun ? 'Would create' : '✓ Created'} .claude/settings.json (SessionStart hook)`);
|
|
604
617
|
updated++;
|
|
605
618
|
}
|
|
606
619
|
|
|
@@ -618,6 +631,12 @@ After displaying the boot output, respond to the user naturally.
|
|
|
618
631
|
|
|
619
632
|
`;
|
|
620
633
|
|
|
634
|
+
const applyClaudeUpdate = (content, dryAction, completedAction) => {
|
|
635
|
+
if (!dryRun) fs.writeFileSync(rootClaudeMd, content);
|
|
636
|
+
console.log(dryRun ? `Would ${dryAction}` : `✓ ${completedAction}`);
|
|
637
|
+
updated++;
|
|
638
|
+
};
|
|
639
|
+
|
|
621
640
|
if (fs.existsSync(rootClaudeMd)) {
|
|
622
641
|
let content = fs.readFileSync(rootClaudeMd, 'utf8');
|
|
623
642
|
const startMarker = '<!-- ATRIS:START';
|
|
@@ -630,9 +649,7 @@ After displaying the boot output, respond to the user naturally.
|
|
|
630
649
|
if (endRaw === -1) {
|
|
631
650
|
// End marker missing — replace from start marker to end of file with fresh block
|
|
632
651
|
content = atrisBlock + content.slice(0, startIdx);
|
|
633
|
-
|
|
634
|
-
console.log('✓ Repaired Atris block in CLAUDE.md (missing end marker)');
|
|
635
|
-
updated++;
|
|
652
|
+
applyClaudeUpdate(content, 'repair Atris block in CLAUDE.md (missing end marker)', 'Repaired Atris block in CLAUDE.md (missing end marker)');
|
|
636
653
|
} else {
|
|
637
654
|
const endIdx = endRaw + endMarker.length;
|
|
638
655
|
const existingBlock = content.slice(startIdx, endIdx);
|
|
@@ -640,25 +657,21 @@ After displaying the boot output, respond to the user naturally.
|
|
|
640
657
|
if (!existingBlock.includes('Atris boot sequence')) {
|
|
641
658
|
// Replace existing Atris block with new version
|
|
642
659
|
content = atrisBlock + content.slice(0, startIdx) + content.slice(endIdx).replace(/^\n+/, '');
|
|
643
|
-
|
|
644
|
-
console.log('✓ Updated Atris block in CLAUDE.md');
|
|
645
|
-
updated++;
|
|
660
|
+
applyClaudeUpdate(content, 'update Atris block in CLAUDE.md', 'Updated Atris block in CLAUDE.md');
|
|
646
661
|
}
|
|
647
662
|
}
|
|
648
663
|
} else {
|
|
649
664
|
// Prepend Atris block
|
|
650
|
-
|
|
651
|
-
console.log('✓ Prepended Atris block to CLAUDE.md');
|
|
652
|
-
updated++;
|
|
665
|
+
applyClaudeUpdate(atrisBlock + content, 'prepend Atris block to CLAUDE.md', 'Prepended Atris block to CLAUDE.md');
|
|
653
666
|
}
|
|
654
667
|
} else {
|
|
655
668
|
// Create new CLAUDE.md with just Atris block
|
|
656
|
-
|
|
657
|
-
console.log('✓ Created CLAUDE.md with Atris block');
|
|
658
|
-
updated++;
|
|
669
|
+
applyClaudeUpdate(atrisBlock.trim() + '\n', 'create CLAUDE.md with Atris block', 'Created CLAUDE.md with Atris block');
|
|
659
670
|
}
|
|
660
671
|
|
|
661
|
-
if (
|
|
672
|
+
if (dryRun) {
|
|
673
|
+
console.log(`\nDry run: ${updated} file(s) would update, ${skipped} unchanged; no changes made.`);
|
|
674
|
+
} else if (updated === 0) {
|
|
662
675
|
ensureWikiScaffold(process.cwd());
|
|
663
676
|
console.log('✓ Already up to date');
|
|
664
677
|
} else {
|