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.
- package/README.md +11 -2
- package/atris/skills/improve/SKILL.md +2 -2
- package/bin/atris.js +7 -1
- package/commands/autopilot.js +562 -39
- package/commands/business.js +14 -9
- package/commands/experiments.js +1 -1
- package/commands/release.js +183 -0
- package/commands/research.js +52 -0
- package/commands/sync.js +102 -13
- package/commands/verify.js +3 -3
- package/commands/wiki.js +45 -25
- package/lib/reward-config.js +24 -0
- package/lib/scorecard.js +16 -2
- package/lib/wiki.js +87 -56
- package/package.json +3 -2
package/commands/business.js
CHANGED
|
@@ -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:
|
|
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 = '
|
|
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
|
|
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 = ['
|
|
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
|
|
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
|
|
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
|
|
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');
|
package/commands/experiments.js
CHANGED
|
@@ -351,7 +351,7 @@ function buildBenchmarkArtifact(name, packDir, options) {
|
|
|
351
351
|
}
|
|
352
352
|
);
|
|
353
353
|
|
|
354
|
-
reviewStatus = execution.
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
59
|
-
console.error(`✗
|
|
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
|
|
132
|
+
console.log(`Updating ${params.name} (${params.slug}) from ${template.label} templates...`);
|
|
66
133
|
console.log(` Target: ${targetAtrisDir}/`);
|
|
67
|
-
console.log(` Source: ${
|
|
134
|
+
console.log(` Source: ${template.dir}`);
|
|
68
135
|
console.log('');
|
|
69
136
|
|
|
70
|
-
const templateFiles = _walkTemplateDir(
|
|
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(
|
|
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
|
|
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 = {
|
|
657
|
+
module.exports = {
|
|
658
|
+
syncAtris,
|
|
659
|
+
syncSkills,
|
|
660
|
+
syncBusinessCanonical,
|
|
661
|
+
syncWorkspaceTemplate,
|
|
662
|
+
resolveWorkspaceTemplate,
|
|
663
|
+
};
|
package/commands/verify.js
CHANGED
|
@@ -444,11 +444,11 @@ function verifyChange(cwd, change) {
|
|
|
444
444
|
};
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
-
//
|
|
447
|
+
// No specific check possible — refuse to auto-pass
|
|
448
448
|
return {
|
|
449
|
-
pass:
|
|
449
|
+
pass: false,
|
|
450
450
|
description: change.description,
|
|
451
|
-
details: '
|
|
451
|
+
details: 'No verifiable check for this change type. Add an explicit verify command.'
|
|
452
452
|
};
|
|
453
453
|
}
|
|
454
454
|
|