atris 3.1.0 → 3.2.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.
@@ -88,6 +88,7 @@ function createCanonicalBusinessWorkspace(targetRoot, bizMeta, options = {}) {
88
88
  throw new Error(`Target already contains .atris/business.json: ${targetRoot}`);
89
89
  }
90
90
 
91
+ const workspaceTemplate = options.templateName || bizMeta.workspace_template || 'business';
91
92
  fs.mkdirSync(atrisMetaDir, { recursive: true });
92
93
  fs.writeFileSync(businessJsonPath, JSON.stringify({
93
94
  business_id: bizMeta.business_id,
@@ -95,11 +96,12 @@ function createCanonicalBusinessWorkspace(targetRoot, bizMeta, options = {}) {
95
96
  name: bizMeta.name,
96
97
  slug: bizMeta.slug,
97
98
  owner_email: bizMeta.owner_email || '',
99
+ workspace_template: workspaceTemplate,
98
100
  created_at: new Date().toISOString(),
99
101
  }, null, 2));
100
102
 
101
- syncBusinessCanonical(targetRoot, bizMeta, { force: false, dryRun: false });
102
- return { targetRoot, businessJsonPath };
103
+ syncBusinessCanonical(targetRoot, bizMeta, { force: false, dryRun: false, templateName: workspaceTemplate });
104
+ return { targetRoot, businessJsonPath, workspaceTemplate };
103
105
  }
104
106
 
105
107
  function detectBusinessSlug(explicitSlug) {
@@ -198,7 +200,7 @@ async function listBusinesses(opts = {}) {
198
200
  * Walk ~/arena/atris-business/ and print a fleet status table for every
199
201
  * customer workspace. Pure local — no API calls, no rate-limit risk.
200
202
  *
201
- * Classifies each dir as: canonical, flat, unbound, nested, bare, or superseded.
203
+ * Classifies each dir as: ready, flat, unbound, nested, bare, or superseded.
202
204
  *
203
205
  * Discovered the need for this during overnight loop tick #3 when we hand-wrote
204
206
  * /tmp/customer_fleet.md. Now any team member can run `atris business list --local`
@@ -253,7 +255,7 @@ function listBusinessesLocal(opts = {}) {
253
255
 
254
256
  let state, action, icon;
255
257
  if (hasBizJson && hasAtris) {
256
- state = 'canonical'; action = 'none'; icon = '🟢';
258
+ state = 'ready'; action = 'none'; icon = '🟢';
257
259
  } else if (hasBizJson && !hasAtris) {
258
260
  state = 'flat'; action = 'migrate to atris/ wrapper'; icon = '🟡';
259
261
  } else if (!hasBizJson && hasAtris) {
@@ -263,7 +265,7 @@ function listBusinessesLocal(opts = {}) {
263
265
  } else if (total < 5) {
264
266
  state = 'bare'; action = 'not yet onboarded'; icon = '⚪';
265
267
  } else {
266
- state = 'flat-unbound'; action = 'needs canonical init'; icon = '🟡';
268
+ state = 'flat-unbound'; action = 'needs business init'; icon = '🟡';
267
269
  }
268
270
 
269
271
  let bizName = name;
@@ -309,7 +311,7 @@ function listBusinessesLocal(opts = {}) {
309
311
  console.log(' CUSTOMER STATE FILES BIZ.JSON ATRIS/ ACTION');
310
312
  console.log(' ' + '─'.repeat(83));
311
313
 
312
- const order = ['canonical', 'flat', 'unbound', 'flat-unbound', 'bare', 'nested', 'superseded'];
314
+ const order = ['ready', 'flat', 'unbound', 'flat-unbound', 'bare', 'nested', 'superseded'];
313
315
  const grouped = {};
314
316
  for (const c of customers) {
315
317
  if (!grouped[c.state]) grouped[c.state] = [];
@@ -678,7 +680,7 @@ async function createBusinessInternal(name, flags = [], mode = 'auto') {
678
680
  }, { here: options.here });
679
681
  console.log(` Local workspace: ${scaffold.targetRoot}/`);
680
682
  } else if (!options.noLocal) {
681
- console.log(' Tip: run `atris business init "<name>"` or add `--workspace` for a local canonical workspace.');
683
+ console.log(' Tip: run `atris business init "<name>"` or add `--workspace` for a local business environment.');
682
684
  }
683
685
 
684
686
  const template = options.template;
@@ -1155,6 +1157,9 @@ async function quickstart() {
1155
1157
  3. Push local state to cloud:
1156
1158
  atris align --fix
1157
1159
 
1160
+ Then open atris/TODO.md and work the starter queue:
1161
+ define the first loop -> add named humans -> write the first recap
1162
+
1158
1163
  Optional:
1159
1164
  atris business connect slack --business my-company
1160
1165
  atris business connect github --business my-company
@@ -1235,9 +1240,9 @@ async function businessCommand(subcommand, ...args) {
1235
1240
  console.log('');
1236
1241
  console.log(' quickstart ← Start here! 3-command guide');
1237
1242
  console.log('');
1238
- console.log(' init <name> Create a canonical business workspace (cloud + local)');
1243
+ console.log(' init <name> Create a business environment (cloud + local)');
1239
1244
  console.log(' workspace <name> Alias for init');
1240
- console.log(' create <name> Create the cloud business; add --workspace for local canonical scaffold');
1245
+ console.log(' create <name> Create the cloud business; add --workspace for a local business environment');
1241
1246
  console.log(' add <slug> Register an existing cloud business');
1242
1247
  console.log(' list Show registered businesses');
1243
1248
  console.log(' team [slug] Show members, roles, and admin access');
@@ -351,7 +351,7 @@ function buildBenchmarkArtifact(name, packDir, options) {
351
351
  }
352
352
  );
353
353
 
354
- reviewStatus = execution.success ? 'pass' : 'fail';
354
+ reviewStatus = execution.verifyPass ? 'pass' : 'fail';
355
355
  reviewSummary = summarizeReview(execution.reviewOutput);
356
356
  tests = inferTestResults(execution.reviewOutput);
357
357
  } catch (error) {
@@ -0,0 +1,183 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execFileSync } = require('child_process');
4
+
5
+ /**
6
+ * atris release [--dry-run] - Tag a release, create GitHub release, draft /launch post
7
+ *
8
+ * - Reads git log since last tag
9
+ * - Determines bump type (minor if any scorecard has reward>=5, else patch)
10
+ * - Bumps package.json version
11
+ * - Commits, tags, pushes
12
+ * - Creates GitHub release via `gh`
13
+ * - Drafts a /launch post (3 emoji bullets)
14
+ */
15
+ async function releaseAtris({ dryRun = false } = {}) {
16
+ const cwd = process.cwd();
17
+
18
+ // 1. Get last tag
19
+ let lastTag;
20
+ try {
21
+ lastTag = execFileSync('git', ['describe', '--tags', '--abbrev=0'], { cwd, encoding: 'utf8' }).trim();
22
+ } catch {
23
+ lastTag = null;
24
+ }
25
+
26
+ // 2. Get git log since last tag
27
+ let logArgs = ['log', '--oneline'];
28
+ if (lastTag) {
29
+ logArgs = ['log', `${lastTag}..HEAD`, '--oneline'];
30
+ }
31
+ let commits;
32
+ try {
33
+ commits = execFileSync('git', logArgs, { cwd, encoding: 'utf8' }).trim();
34
+ } catch {
35
+ commits = '';
36
+ }
37
+
38
+ if (!commits) {
39
+ console.log('no new commits since last tag' + (lastTag ? ` (${lastTag})` : '') + '. nothing to release.');
40
+ return;
41
+ }
42
+
43
+ const commitLines = commits.split('\n').filter(Boolean);
44
+ console.log('');
45
+ console.log(`commits since ${lastTag || 'beginning'}: ${commitLines.length}`);
46
+
47
+ // 3. Build changelog
48
+ const changelog = commitLines.map(l => `- ${l}`).join('\n');
49
+
50
+ // 4. Determine bump type — minor if any scorecard has reward >= 5, else patch
51
+ const bumpType = determineBumpType(cwd);
52
+ console.log(`bump type: ${bumpType}`);
53
+
54
+ // 5. Read current version and compute next
55
+ const pkgPath = path.join(cwd, 'package.json');
56
+ if (!fs.existsSync(pkgPath)) {
57
+ console.log('no package.json found. cannot bump version.');
58
+ return;
59
+ }
60
+ const pkgRaw = fs.readFileSync(pkgPath, 'utf8');
61
+ const pkg = JSON.parse(pkgRaw);
62
+ const currentVersion = pkg.version;
63
+ const nextVersion = bumpVersion(currentVersion, bumpType);
64
+
65
+ console.log(`version: ${currentVersion} → ${nextVersion}`);
66
+ console.log('');
67
+
68
+ // 6. Show changelog
69
+ console.log('changelog:');
70
+ console.log(changelog);
71
+ console.log('');
72
+
73
+ if (dryRun) {
74
+ console.log('--- dry-run: draft release ---');
75
+ console.log(`tag: v${nextVersion}`);
76
+ console.log(`title: v${nextVersion}`);
77
+ console.log('');
78
+ printLaunchPost(nextVersion, commitLines);
79
+ return;
80
+ }
81
+
82
+ // 7. Bump package.json
83
+ const updatedPkgRaw = pkgRaw.replace(
84
+ `"version": "${currentVersion}"`,
85
+ `"version": "${nextVersion}"`
86
+ );
87
+ fs.writeFileSync(pkgPath, updatedPkgRaw);
88
+ console.log(`bumped package.json to ${nextVersion}`);
89
+
90
+ // 8. Commit
91
+ execFileSync('git', ['add', 'package.json'], { cwd });
92
+ execFileSync('git', ['commit', '-m', `v${nextVersion}`], { cwd });
93
+ console.log('committed');
94
+
95
+ // 9. Tag
96
+ execFileSync('git', ['tag', `v${nextVersion}`], { cwd });
97
+ console.log(`tagged v${nextVersion}`);
98
+
99
+ // 10. Push + push tags
100
+ try {
101
+ execFileSync('git', ['push'], { cwd });
102
+ execFileSync('git', ['push', '--tags'], { cwd });
103
+ console.log('pushed');
104
+ } catch (err) {
105
+ console.log('push failed — you may need to push manually');
106
+ }
107
+
108
+ // 11. Create GitHub release via gh
109
+ try {
110
+ execFileSync('gh', ['release', 'create', `v${nextVersion}`,
111
+ '--title', `v${nextVersion}`,
112
+ '--notes', changelog
113
+ ], { cwd });
114
+ console.log(`github release created: v${nextVersion}`);
115
+ } catch (err) {
116
+ console.log('gh release create failed — install gh or create release manually');
117
+ }
118
+
119
+ // 12. Draft launch post
120
+ console.log('');
121
+ printLaunchPost(nextVersion, commitLines);
122
+ }
123
+
124
+ /**
125
+ * Check scorecards for reward >= 5 → minor bump. Otherwise patch.
126
+ */
127
+ function determineBumpType(cwd) {
128
+ const scorecardsDir = path.join(cwd, 'atris', 'scorecards');
129
+ if (!fs.existsSync(scorecardsDir)) return 'patch';
130
+
131
+ try {
132
+ const files = fs.readdirSync(scorecardsDir).filter(f => f.endsWith('.md'));
133
+ for (const file of files) {
134
+ const content = fs.readFileSync(path.join(scorecardsDir, file), 'utf8');
135
+ const rewardMatch = content.match(/reward[:\s]+(\d+)/i);
136
+ if (rewardMatch && parseInt(rewardMatch[1], 10) >= 5) {
137
+ return 'minor';
138
+ }
139
+ }
140
+ } catch {
141
+ // ignore
142
+ }
143
+ return 'patch';
144
+ }
145
+
146
+ /**
147
+ * Bump a semver string: "1.2.3" + "patch" → "1.2.4", "minor" → "1.3.0"
148
+ */
149
+ function bumpVersion(version, type) {
150
+ const parts = version.split('.').map(Number);
151
+ if (type === 'minor') {
152
+ parts[1]++;
153
+ parts[2] = 0;
154
+ } else {
155
+ parts[2]++;
156
+ }
157
+ return parts.join('.');
158
+ }
159
+
160
+ /**
161
+ * Print a 3-emoji-bullet launch post (Twitter + LinkedIn format)
162
+ */
163
+ function printLaunchPost(version, commitLines) {
164
+ // Pick top 3 changes (dedupe, trim hashes)
165
+ const topChanges = commitLines
166
+ .slice(0, 3)
167
+ .map(l => l.replace(/^[a-f0-9]+\s+/, ''));
168
+
169
+ const emojis = ['🚀', '⚡', '🔧'];
170
+
171
+ console.log('--- launch post draft ---');
172
+ console.log('');
173
+ console.log(`Atris v${version} is out.`);
174
+ console.log('');
175
+ topChanges.forEach((change, i) => {
176
+ console.log(`${emojis[i] || '•'} ${change}`);
177
+ });
178
+ console.log('');
179
+ console.log('npm i -g atris');
180
+ console.log('--- end launch post ---');
181
+ }
182
+
183
+ module.exports = { releaseAtris };
@@ -0,0 +1,52 @@
1
+ const { initResearchWorkspace } = require('./business');
2
+
3
+ async function researchQuickstart() {
4
+ console.log(`
5
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6
+ Start a Research Lab in 3 Commands
7
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8
+
9
+ 1. Create:
10
+ atris research init "Frontier Lab"
11
+
12
+ 2. Open the local workspace:
13
+ cd ~/arena/atris-business/frontier-lab
14
+
15
+ 3. Push local state to cloud:
16
+ atris align --fix
17
+
18
+ The research template starts with:
19
+ hypotheses + experiment lanes
20
+ eval-first reward policy
21
+ literature + findings workflow
22
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
23
+ `);
24
+ }
25
+
26
+ async function researchCommand(subcommand, ...args) {
27
+ switch (subcommand) {
28
+ case 'init':
29
+ case 'workspace':
30
+ case 'create':
31
+ await initResearchWorkspace(args[0], ...args.slice(1));
32
+ break;
33
+ case 'quickstart':
34
+ case 'start':
35
+ case 'guide':
36
+ await researchQuickstart();
37
+ break;
38
+ default:
39
+ console.log('Usage: atris research <command> [args]');
40
+ console.log('');
41
+ console.log(' quickstart ← Start here! 3-command guide');
42
+ console.log('');
43
+ console.log(' init <name> Create a research lab workspace (cloud + local)');
44
+ console.log(' workspace <name> Alias for init');
45
+ console.log(' create <name> Alias for init');
46
+ }
47
+ }
48
+
49
+ module.exports = {
50
+ researchCommand,
51
+ researchQuickstart,
52
+ };
package/commands/sync.js CHANGED
@@ -3,7 +3,17 @@ const path = require('path');
3
3
  const os = require('os');
4
4
  const { ensureWikiScaffold } = require('../lib/wiki');
5
5
 
6
- const BUSINESS_TEMPLATE_DIR = path.join(__dirname, '..', 'templates', 'business-canonical');
6
+ const TEMPLATE_ROOT_DIR = path.join(__dirname, '..', 'templates');
7
+ const WORKSPACE_TEMPLATES = {
8
+ business: {
9
+ dir: path.join(TEMPLATE_ROOT_DIR, 'business-starter'),
10
+ label: 'business environment',
11
+ },
12
+ research: {
13
+ dir: path.join(TEMPLATE_ROOT_DIR, 'research-canonical'),
14
+ label: 'research lab environment',
15
+ },
16
+ };
7
17
 
8
18
  /**
9
19
  * Walk a directory and return relative file paths.
@@ -25,7 +35,7 @@ function _walkTemplateDir(dir, base = dir) {
25
35
  }
26
36
 
27
37
  /**
28
- * Substitute {{name}}, {{slug}}, {{owner_email}} in template content.
38
+ * Substitute workspace metadata in template content.
29
39
  */
30
40
  function _substituteParams(content, params) {
31
41
  return content
@@ -33,7 +43,56 @@ function _substituteParams(content, params) {
33
43
  .replace(/\{\{slug\}\}/g, params.slug || 'business')
34
44
  .replace(/\{\{owner_email\}\}/g, params.owner_email || '')
35
45
  .replace(/\{\{business_id\}\}/g, params.business_id || '')
36
- .replace(/\{\{workspace_id\}\}/g, params.workspace_id || '');
46
+ .replace(/\{\{workspace_id\}\}/g, params.workspace_id || '')
47
+ .replace(/\{\{today\}\}/g, params.today || new Date().toISOString().slice(0, 10))
48
+ .replace(/\{\{workspace_template\}\}/g, params.workspace_template || 'business');
49
+ }
50
+
51
+ function resolveWorkspaceTemplate(templateName = 'business') {
52
+ const normalized = String(templateName || 'business').toLowerCase();
53
+ if (normalized === 'research-lab' || normalized === 'researchlab' || normalized === 'lab') {
54
+ return { key: 'research', ...WORKSPACE_TEMPLATES.research };
55
+ }
56
+ const template = WORKSPACE_TEMPLATES[normalized];
57
+ if (!template) return null;
58
+ return { key: normalized, ...template };
59
+ }
60
+
61
+ function _ensureWorkspaceState(targetRoot, params, options = {}) {
62
+ const dryRun = options.dryRun === true;
63
+ const metaDir = path.join(targetRoot, '.atris');
64
+ const stateDir = path.join(metaDir, 'state');
65
+ const created = [];
66
+
67
+ const files = [
68
+ {
69
+ relPath: '_sync.json',
70
+ content: `${JSON.stringify({
71
+ workspace_slug: params.slug || 'business',
72
+ business_id: params.business_id || '',
73
+ workspace_id: params.workspace_id || '',
74
+ workspace_template: params.workspace_template || 'business',
75
+ status: 'initialized-local',
76
+ updated_at: new Date().toISOString(),
77
+ source: 'workspace template bootstrap',
78
+ }, null, 2)}\n`,
79
+ },
80
+ { relPath: 'events.jsonl', content: '' },
81
+ { relPath: 'episodes.jsonl', content: '' },
82
+ { relPath: 'scorecards.jsonl', content: '' },
83
+ ];
84
+
85
+ for (const file of files) {
86
+ const fullPath = path.join(stateDir, file.relPath);
87
+ if (fs.existsSync(fullPath)) continue;
88
+ created.push(path.join('.atris', 'state', file.relPath));
89
+ if (!dryRun) {
90
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
91
+ fs.writeFileSync(fullPath, file.content);
92
+ }
93
+ }
94
+
95
+ return created;
37
96
  }
38
97
 
39
98
  /**
@@ -43,36 +102,44 @@ function _substituteParams(content, params) {
43
102
  * Default: NEVER overwrites existing files (preserves customizations).
44
103
  * --force: overwrites existing canonical files (bumps to latest).
45
104
  */
46
- function syncBusinessCanonical(targetRoot, bizMeta, options = {}) {
105
+ function syncWorkspaceTemplate(targetRoot, bizMeta, options = {}) {
106
+ const template = resolveWorkspaceTemplate(options.templateName || bizMeta.workspace_template || 'business');
107
+ if (!template) {
108
+ console.error(`✗ Unknown workspace template: ${options.templateName || bizMeta.workspace_template}`);
109
+ process.exit(1);
110
+ }
111
+
47
112
  const params = {
48
113
  slug: bizMeta.slug || 'business',
49
114
  name: bizMeta.name || bizMeta.slug || 'this business',
50
115
  owner_email: bizMeta.owner_email || '',
51
116
  business_id: bizMeta.business_id || '',
52
117
  workspace_id: bizMeta.workspace_id || '',
118
+ today: new Date().toISOString().slice(0, 10),
119
+ workspace_template: template.key,
53
120
  };
54
121
  const force = options.force != null ? options.force : process.argv.includes('--force');
55
122
  const dryRun = options.dryRun != null ? options.dryRun : process.argv.includes('--dry-run');
56
123
  const targetAtrisDir = path.join(targetRoot, 'atris');
57
124
 
58
- if (!fs.existsSync(BUSINESS_TEMPLATE_DIR)) {
59
- console.error(`✗ Canonical template directory not found: ${BUSINESS_TEMPLATE_DIR}`);
125
+ if (!fs.existsSync(template.dir)) {
126
+ console.error(`✗ Workspace template directory not found: ${template.dir}`);
60
127
  console.error(' Your atris-cli installation may be incomplete.');
61
128
  process.exit(1);
62
129
  }
63
130
 
64
131
  console.log('');
65
- console.log(`Updating ${params.name} (${params.slug}) from canonical templates...`);
132
+ console.log(`Updating ${params.name} (${params.slug}) from ${template.label} templates...`);
66
133
  console.log(` Target: ${targetAtrisDir}/`);
67
- console.log(` Source: ${BUSINESS_TEMPLATE_DIR}`);
134
+ console.log(` Source: ${template.dir}`);
68
135
  console.log('');
69
136
 
70
- const templateFiles = _walkTemplateDir(BUSINESS_TEMPLATE_DIR).sort();
137
+ const templateFiles = _walkTemplateDir(template.dir).sort();
71
138
  let added = 0, updated = 0, skipped = 0, preserved = 0;
72
139
  const addedList = [], updatedList = [], preservedList = [];
73
140
 
74
141
  for (const relPath of templateFiles) {
75
- const templatePath = path.join(BUSINESS_TEMPLATE_DIR, relPath);
142
+ const templatePath = path.join(template.dir, relPath);
76
143
  const targetPath = path.join(targetAtrisDir, relPath);
77
144
  let templateContent;
78
145
  try { templateContent = fs.readFileSync(templatePath, 'utf-8'); } catch { continue; }
@@ -97,6 +164,8 @@ function syncBusinessCanonical(targetRoot, bizMeta, options = {}) {
97
164
  }
98
165
  }
99
166
 
167
+ const stateAddedList = _ensureWorkspaceState(targetRoot, params, { dryRun });
168
+
100
169
  console.log(` Added: ${added}`);
101
170
  console.log(` Updated: ${updated} ${force ? '' : '(--force to enable)'}`);
102
171
  console.log(` Preserved: ${preserved} (existing customizations kept)`);
@@ -115,10 +184,15 @@ function syncBusinessCanonical(targetRoot, bizMeta, options = {}) {
115
184
  if (updatedList.length > 15) console.log(` ... +${updatedList.length - 15} more`);
116
185
  console.log('');
117
186
  }
187
+ if (stateAddedList.length > 0) {
188
+ console.log(' State files:');
189
+ stateAddedList.forEach(p => console.log(` + ${p}`));
190
+ console.log('');
191
+ }
118
192
 
119
193
  if (dryRun) {
120
194
  console.log(' (--dry-run, no changes made)');
121
- } else if (added === 0 && updated === 0) {
195
+ } else if (added === 0 && updated === 0 && stateAddedList.length === 0) {
122
196
  ensureWikiScaffold(targetRoot);
123
197
  console.log(' ✓ Already up to date');
124
198
  } else {
@@ -127,13 +201,22 @@ function syncBusinessCanonical(targetRoot, bizMeta, options = {}) {
127
201
  }
128
202
  }
129
203
 
204
+ function syncBusinessCanonical(targetRoot, bizMeta, options = {}) {
205
+ return syncWorkspaceTemplate(targetRoot, bizMeta, {
206
+ ...options,
207
+ templateName: options.templateName || bizMeta.workspace_template || 'business',
208
+ });
209
+ }
210
+
130
211
  function syncAtris() {
131
212
  // Business mode detection: if .atris/business.json exists, use canonical templates
132
213
  const bizFile = path.join(process.cwd(), '.atris', 'business.json');
133
214
  if (fs.existsSync(bizFile)) {
134
215
  try {
135
216
  const bizMeta = JSON.parse(fs.readFileSync(bizFile, 'utf8'));
136
- return syncBusinessCanonical(process.cwd(), bizMeta);
217
+ return syncWorkspaceTemplate(process.cwd(), bizMeta, {
218
+ templateName: bizMeta.workspace_template || 'business',
219
+ });
137
220
  } catch (e) {
138
221
  console.error(`✗ Failed to read .atris/business.json: ${e.message}`);
139
222
  process.exit(1);
@@ -571,4 +654,10 @@ function syncSkills({ silent = false } = {}) {
571
654
  return updated;
572
655
  }
573
656
 
574
- module.exports = { syncAtris, syncSkills, syncBusinessCanonical };
657
+ module.exports = {
658
+ syncAtris,
659
+ syncSkills,
660
+ syncBusinessCanonical,
661
+ syncWorkspaceTemplate,
662
+ resolveWorkspaceTemplate,
663
+ };
@@ -444,11 +444,11 @@ function verifyChange(cwd, change) {
444
444
  };
445
445
  }
446
446
 
447
- // Generic check - always pass (can't verify without specifics)
447
+ // No specific check possible refuse to auto-pass
448
448
  return {
449
- pass: true,
449
+ pass: false,
450
450
  description: change.description,
451
- details: 'Manual verification recommended'
451
+ details: 'No verifiable check for this change type. Add an explicit verify command.'
452
452
  };
453
453
  }
454
454