@bitmagic/cli 0.1.25 → 0.1.27
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 +22 -0
- package/dist/commands/dev.js +4 -0
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/forge.js +32 -21
- package/dist/commands/forge.js.map +1 -1
- package/dist/commands/generate.js +117 -56
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/upgrade.js +7 -0
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/editor/previews.d.ts +23 -0
- package/dist/editor/previews.js +108 -0
- package/dist/editor/previews.js.map +1 -0
- package/dist/editor/server.js +111 -3
- package/dist/editor/server.js.map +1 -1
- package/dist/editor/shell-page.js +406 -5
- package/dist/editor/shell-page.js.map +1 -1
- package/dist/project/jobs.d.ts +67 -0
- package/dist/project/jobs.js +295 -0
- package/dist/project/jobs.js.map +1 -0
- package/dist/publish/fingerprint.js +8 -1
- package/dist/publish/fingerprint.js.map +1 -1
- package/dist/scaffold/claude-settings.d.ts +7 -1
- package/dist/scaffold/claude-settings.js +34 -2
- package/dist/scaffold/claude-settings.js.map +1 -1
- package/dist/scaffold/engine-download.d.ts +10 -5
- package/dist/scaffold/engine-download.js +14 -8
- package/dist/scaffold/engine-download.js.map +1 -1
- package/dist/scaffold/project-files.d.ts +27 -0
- package/dist/scaffold/project-files.js +444 -8
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/scaffold/project.d.ts +17 -0
- package/dist/scaffold/project.js +26 -2
- package/dist/scaffold/project.js.map +1 -1
- package/dist/scaffold/upgrade-project.js +9 -1
- package/dist/scaffold/upgrade-project.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.bitmagic/jobs/` — what a running `bitmagic dev` can see of generations it did not start.
|
|
3
|
+
*
|
|
4
|
+
* Every `bitmagic generate`, `forge` and `cover` writes `world.json` exactly once, at the very end,
|
|
5
|
+
* and writes nothing anywhere else while it runs. A `generate skybox` that takes four minutes
|
|
6
|
+
* leaves zero on-disk trace for those four minutes. Since each of those commands is its own
|
|
7
|
+
* process, a `bitmagic dev` in the next window has nothing to read and nothing to show — which is
|
|
8
|
+
* precisely the stretch of time the creator most wants to watch.
|
|
9
|
+
*
|
|
10
|
+
* So each generation announces itself here: one file per job, written on start, updated as its
|
|
11
|
+
* progress lines land, removed when it succeeds.
|
|
12
|
+
*
|
|
13
|
+
* ── Why a file rather than a message to the dev server ───────────────────────────────────────
|
|
14
|
+
*
|
|
15
|
+
* A loopback POST (the trick `bitmagic reload` uses via `dev-handle.ts`) would be lower latency,
|
|
16
|
+
* but it only works when `dev` happened to be running at the moment the generation started, and it
|
|
17
|
+
* survives neither a `dev` restart nor a `dev` opened halfway through a twenty-minute forge. A file
|
|
18
|
+
* is readable by whoever turns up whenever they turn up — including the creator's agent, which can
|
|
19
|
+
* `cat` this directory to see what it has in flight. The dev sidecar watches the directory, so the
|
|
20
|
+
* latency is a filesystem event, not a poll.
|
|
21
|
+
*
|
|
22
|
+
* ── Removed on success, kept on failure ──────────────────────────────────────────────────────
|
|
23
|
+
*
|
|
24
|
+
* A finished asset is in `world.json`, and that IS the completion record — the browser reloads
|
|
25
|
+
* because the file changed, and the asset appears in the panel. A record left behind saying "done"
|
|
26
|
+
* would be a second, staler source of truth for the same fact.
|
|
27
|
+
*
|
|
28
|
+
* A failure is the opposite: nothing else records it. `world.json` is untouched, so the panel would
|
|
29
|
+
* simply show a job vanishing with no asset ever appearing. The record stays, carrying the error.
|
|
30
|
+
*
|
|
31
|
+
* ── Staleness is the reader's problem ────────────────────────────────────────────────────────
|
|
32
|
+
*
|
|
33
|
+
* A SIGKILLed generation never removes its file, so a `running` record is a claim, not a fact. The
|
|
34
|
+
* `pid` is on the record for exactly that: `readJobs` checks liveness and reports a job whose
|
|
35
|
+
* process is gone as failed. This is the rule `game-play-agent/src/mastra/utils/hq-asset-jobs.ts`
|
|
36
|
+
* already arrived at for the web lane's job list, for the same reason.
|
|
37
|
+
*
|
|
38
|
+
* Note the deliberate contrast with `dev-handle.ts`, which records a pid and explicitly declines to
|
|
39
|
+
* check it: there, a stale handle is self-correcting (the reload just fails to connect and says so).
|
|
40
|
+
* Here nothing else would ever notice, so the check earns its keep.
|
|
41
|
+
*/
|
|
42
|
+
import * as fs from 'fs';
|
|
43
|
+
import * as path from 'path';
|
|
44
|
+
export const JOBS_DIR = path.join('.bitmagic', 'jobs');
|
|
45
|
+
/** Failed records are kept so the creator can see why nothing appeared — but not forever. */
|
|
46
|
+
const FAILED_RECORD_TTL_MS = 24 * 60 * 60 * 1000;
|
|
47
|
+
export function jobsDirPath(root) {
|
|
48
|
+
return path.join(root, JOBS_DIR);
|
|
49
|
+
}
|
|
50
|
+
function jobPath(root, jobId) {
|
|
51
|
+
return path.join(jobsDirPath(root), `${jobId}.json`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Ids only have to be unique among the jobs running on one machine at one moment, and they end up
|
|
55
|
+
* in a filename. pid plus a counter plus a short random tail covers concurrent commands without
|
|
56
|
+
* needing a clock the tests would then have to control.
|
|
57
|
+
*/
|
|
58
|
+
let sequence = 0;
|
|
59
|
+
function newJobId(kind) {
|
|
60
|
+
sequence += 1;
|
|
61
|
+
return `${kind}-${process.pid}-${sequence}-${Math.random().toString(36).slice(2, 8)}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Temp file then rename, the same discipline `forge/local-store.ts` uses: the sidecar watches this
|
|
65
|
+
* directory and will read a record the instant it appears, so a half-written file is a JSON parse
|
|
66
|
+
* error in the dev view rather than a merely-late update.
|
|
67
|
+
*/
|
|
68
|
+
function writeRecord(root, record) {
|
|
69
|
+
const file = jobPath(root, record.jobId);
|
|
70
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
71
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
72
|
+
fs.writeFileSync(temp, `${JSON.stringify(record, null, 2)}\n`);
|
|
73
|
+
fs.renameSync(temp, file);
|
|
74
|
+
}
|
|
75
|
+
function isRecord(value) {
|
|
76
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
77
|
+
}
|
|
78
|
+
function parseRecord(raw) {
|
|
79
|
+
let parsed;
|
|
80
|
+
try {
|
|
81
|
+
parsed = JSON.parse(raw);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (!isRecord(parsed))
|
|
87
|
+
return null;
|
|
88
|
+
const { jobId, kind, label, status, message, pid, startedAt, updatedAt } = parsed;
|
|
89
|
+
if (typeof jobId !== 'string' || typeof kind !== 'string' || typeof pid !== 'number')
|
|
90
|
+
return null;
|
|
91
|
+
return {
|
|
92
|
+
jobId,
|
|
93
|
+
kind: kind,
|
|
94
|
+
label: typeof label === 'string' ? label : '',
|
|
95
|
+
...(typeof parsed.assetId === 'string' ? { assetId: parsed.assetId } : {}),
|
|
96
|
+
status: status === 'failed' ? 'failed' : 'running',
|
|
97
|
+
message: typeof message === 'string' ? message : '',
|
|
98
|
+
pid,
|
|
99
|
+
startedAt: typeof startedAt === 'string' ? startedAt : '',
|
|
100
|
+
updatedAt: typeof updatedAt === 'string' ? updatedAt : '',
|
|
101
|
+
...(typeof parsed.error === 'string' ? { error: parsed.error } : {}),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Is the process that wrote this record still alive?
|
|
106
|
+
*
|
|
107
|
+
* Signal 0 performs the permission and existence checks without delivering anything. EPERM means
|
|
108
|
+
* the process exists but belongs to someone else — alive, as far as this question goes.
|
|
109
|
+
*/
|
|
110
|
+
function isProcessAlive(pid) {
|
|
111
|
+
if (pid <= 0)
|
|
112
|
+
return false;
|
|
113
|
+
try {
|
|
114
|
+
process.kill(pid, 0);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return error.code === 'EPERM';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Every job this project knows about, newest first, with dead `running` records reported as
|
|
123
|
+
* failed rather than left spinning in the dev view forever.
|
|
124
|
+
*
|
|
125
|
+
* Unreadable and unparseable files are skipped, not thrown: a record being written right now is a
|
|
126
|
+
* normal thing for a directory that several processes append to, and losing one tick of one job is
|
|
127
|
+
* not worth failing a read that also carries five healthy ones.
|
|
128
|
+
*/
|
|
129
|
+
export function readJobs(root) {
|
|
130
|
+
let names;
|
|
131
|
+
try {
|
|
132
|
+
names = fs.readdirSync(jobsDirPath(root));
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
const jobs = [];
|
|
138
|
+
for (const name of names) {
|
|
139
|
+
if (!name.endsWith('.json'))
|
|
140
|
+
continue;
|
|
141
|
+
let raw;
|
|
142
|
+
try {
|
|
143
|
+
raw = fs.readFileSync(path.join(jobsDirPath(root), name), 'utf-8');
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const record = parseRecord(raw);
|
|
149
|
+
if (record === null)
|
|
150
|
+
continue;
|
|
151
|
+
if (record.status === 'running' && !isProcessAlive(record.pid)) {
|
|
152
|
+
jobs.push({
|
|
153
|
+
...record,
|
|
154
|
+
status: 'failed',
|
|
155
|
+
error: 'The generation stopped before it finished — its process is gone.',
|
|
156
|
+
});
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
jobs.push(record);
|
|
160
|
+
}
|
|
161
|
+
return jobs.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Drop failed records older than a day. Called by `dev` at startup rather than on a timer: this is
|
|
165
|
+
* housekeeping, and the moment someone opens the dev view is the moment a day-old failure stops
|
|
166
|
+
* being worth showing.
|
|
167
|
+
*/
|
|
168
|
+
export function pruneJobs(root, now = Date.now()) {
|
|
169
|
+
let names;
|
|
170
|
+
try {
|
|
171
|
+
names = fs.readdirSync(jobsDirPath(root));
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
let removed = 0;
|
|
177
|
+
for (const name of names) {
|
|
178
|
+
if (!name.endsWith('.json'))
|
|
179
|
+
continue;
|
|
180
|
+
const file = path.join(jobsDirPath(root), name);
|
|
181
|
+
let record = null;
|
|
182
|
+
try {
|
|
183
|
+
record = parseRecord(fs.readFileSync(file, 'utf-8'));
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Fall through: an unreadable record is exactly as useless as an expired one.
|
|
187
|
+
}
|
|
188
|
+
const startedMs = record === null ? 0 : Date.parse(record.startedAt);
|
|
189
|
+
const expired = !Number.isFinite(startedMs) || now - startedMs > FAILED_RECORD_TTL_MS;
|
|
190
|
+
// A live `running` job is never pruned however old it looks — a forge legitimately runs for
|
|
191
|
+
// twenty minutes, and a clock skew must not delete a job that is still working. A `running`
|
|
192
|
+
// record whose process is gone is a failure that never got to say so, and ages out like one.
|
|
193
|
+
if (record !== null && record.status === 'running' && isProcessAlive(record.pid))
|
|
194
|
+
continue;
|
|
195
|
+
if (!expired)
|
|
196
|
+
continue;
|
|
197
|
+
try {
|
|
198
|
+
fs.rmSync(file, { force: true });
|
|
199
|
+
removed += 1;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// A record we cannot delete is a stale file, never a failed startup.
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return removed;
|
|
206
|
+
}
|
|
207
|
+
export function startJob(options) {
|
|
208
|
+
const now = new Date().toISOString();
|
|
209
|
+
const record = {
|
|
210
|
+
jobId: newJobId(options.kind),
|
|
211
|
+
kind: options.kind,
|
|
212
|
+
label: options.label,
|
|
213
|
+
...(options.assetId === undefined ? {} : { assetId: options.assetId }),
|
|
214
|
+
status: 'running',
|
|
215
|
+
message: 'Starting…',
|
|
216
|
+
pid: process.pid,
|
|
217
|
+
startedAt: now,
|
|
218
|
+
updatedAt: now,
|
|
219
|
+
};
|
|
220
|
+
writeRecord(options.root, record);
|
|
221
|
+
return {
|
|
222
|
+
jobId: record.jobId,
|
|
223
|
+
update: (message) => {
|
|
224
|
+
record.message = message;
|
|
225
|
+
record.updatedAt = new Date().toISOString();
|
|
226
|
+
writeRecord(options.root, record);
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Run `body` with its progress recorded for anyone watching.
|
|
232
|
+
*
|
|
233
|
+
* The `log` handed to the body is the caller's own logger with a tap on it, so every line a command
|
|
234
|
+
* already prints also becomes the job's current message. That is deliberate reuse rather than a
|
|
235
|
+
* second progress vocabulary to keep in step — the dev sidecar's in-process HQ job has worked this
|
|
236
|
+
* way since it shipped, and it means a command gains live progress in the dev view without a single
|
|
237
|
+
* new call site inside it.
|
|
238
|
+
*
|
|
239
|
+
* **Never fails the generation it is watching.** Bookkeeping that breaks a paid, minutes-long
|
|
240
|
+
* generation would be a far worse bug than the missing progress line it is trying to provide, so
|
|
241
|
+
* every filesystem error here is swallowed. The body's own errors propagate untouched, after being
|
|
242
|
+
* recorded.
|
|
243
|
+
*/
|
|
244
|
+
export async function withJobRecord(options, body) {
|
|
245
|
+
let handle = null;
|
|
246
|
+
try {
|
|
247
|
+
handle = startJob(options);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// See above: no record is a worse dev view, not a failed generation.
|
|
251
|
+
}
|
|
252
|
+
const log = (message) => {
|
|
253
|
+
options.log(message);
|
|
254
|
+
try {
|
|
255
|
+
handle?.update(message);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
// Same.
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
try {
|
|
262
|
+
const result = await body(log);
|
|
263
|
+
if (handle)
|
|
264
|
+
removeJob(options.root, handle.jobId);
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
if (handle) {
|
|
269
|
+
failJob(options.root, handle.jobId, error instanceof Error ? error.message : String(error));
|
|
270
|
+
}
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/** Success: the asset is in world.json, and that is the record. */
|
|
275
|
+
export function removeJob(root, jobId) {
|
|
276
|
+
try {
|
|
277
|
+
fs.rmSync(jobPath(root, jobId), { force: true });
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// A record we cannot delete reads as a dead process on the next poll, which is close enough.
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/** Failure: nothing else records it, so the record stays and carries the reason. */
|
|
284
|
+
export function failJob(root, jobId, error) {
|
|
285
|
+
try {
|
|
286
|
+
const existing = parseRecord(fs.readFileSync(jobPath(root, jobId), 'utf-8'));
|
|
287
|
+
if (existing === null)
|
|
288
|
+
return;
|
|
289
|
+
writeRecord(root, { ...existing, status: 'failed', error, updatedAt: new Date().toISOString() });
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
// See withJobRecord: bookkeeping never fails the thing it is bookkeeping for.
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=jobs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jobs.js","sourceRoot":"","sources":["../../src/project/jobs.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,6 +23,13 @@ import * as path from 'path';
|
|
|
23
23
|
* mechanism, because a false "fresh" ships an unverified bundle while a false "stale" only costs
|
|
24
24
|
* one `bitmagic verify`.
|
|
25
25
|
*
|
|
26
|
+
* `mechanics-plan.md` earns its place the same way, and for a sharper version of the reason: the
|
|
27
|
+
* agent is told to rewrite it after every slice — moving shipped items out of the backlog and
|
|
28
|
+
* re-ranking the rest is the whole point of the file. Left in, the ordinary
|
|
29
|
+
* `bitmagic verify` → update the backlog → `bitmagic publish` sequence exits 3 over prose, which
|
|
30
|
+
* teaches agents to reach for `--force`, the one flag that also waves through a genuinely stale
|
|
31
|
+
* verify. Nothing imports it and `vite.publish.config.js` cannot put prose in the bundle.
|
|
32
|
+
*
|
|
26
33
|
* `GAME-DESIGN.md` earns its place on the same "no build tool reads it" test: nothing imports it,
|
|
27
34
|
* `vite.publish.config.js` cannot put prose in the bundle, and it exists to be REWRITTEN as the
|
|
28
35
|
* game evolves — it is the input `bitmagic cover` reads. Left in, the ordinary
|
|
@@ -33,7 +40,7 @@ import * as path from 'path';
|
|
|
33
40
|
*/
|
|
34
41
|
const EXCLUDED_DIRS = new Set(['.bitmagic', 'node_modules', 'dist', '.vite-cache', '.git']);
|
|
35
42
|
/** See EXCLUDED_DIRS: files that can never change what the bundle contains. */
|
|
36
|
-
const EXCLUDED_FILES = new Set(['.DS_Store', 'GAME-DESIGN.md']);
|
|
43
|
+
const EXCLUDED_FILES = new Set(['.DS_Store', 'GAME-DESIGN.md', 'mechanics-plan.md']);
|
|
37
44
|
/**
|
|
38
45
|
* A relative path as the fingerprint hashes it: always forward-slashed.
|
|
39
46
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/publish/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/publish/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;AAE5F,+EAA+E;AAC/E,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,gBAAgB,EAAE,mBAAmB,CAAC,CAAC,CAAC;AAErF;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,YAAoB,EAAE,MAAc,IAAI,CAAC,GAAG;IACtE,OAAO,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,IAAI,CAAC,GAAW,EAAE,IAAY,EAAE,GAAa;IACpD,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC5C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QAC9C,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1B,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAChE,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACxB,KAAK,CAAC,IAAI,EAAE,CAAC;IAEb,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAClC,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC"}
|
|
@@ -29,7 +29,13 @@ export interface ReloadHookMerge {
|
|
|
29
29
|
/** Present only when `contents` is null *because* the file could not be merged. */
|
|
30
30
|
skipped?: string;
|
|
31
31
|
}
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* The settings a project with no `.claude/settings.json` at all gets.
|
|
34
|
+
*
|
|
35
|
+
* Note the asymmetry with `mergeReloadHook` below: an existing settings file gains the Stop hook
|
|
36
|
+
* and nothing else. Permissions are the creator's own security posture, and adding entries to a
|
|
37
|
+
* file they already own is a different kind of act from seeding one that does not exist yet.
|
|
38
|
+
*/
|
|
33
39
|
export declare function renderClaudeSettings(): string;
|
|
34
40
|
/**
|
|
35
41
|
* Add the Stop hook to an existing settings file, preserving everything else in it.
|
|
@@ -33,9 +33,41 @@ function stringify(settings) {
|
|
|
33
33
|
function reloadMatcher() {
|
|
34
34
|
return { hooks: [{ type: 'command', command: RELOAD_HOOK_COMMAND }] };
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* The `bitmagic` commands a new project pre-approves, so the agent is not prompted for them.
|
|
38
|
+
*
|
|
39
|
+
* Every entry is free, local and idempotent, and every one is something AGENTS.md tells the agent
|
|
40
|
+
* to run repeatedly — `check` after each edit, `reload` at the end of each turn, `verify` before
|
|
41
|
+
* declaring anything done. A prompt on those is pure friction.
|
|
42
|
+
*
|
|
43
|
+
* What is deliberately ABSENT is the point of the list. A blanket `Bash(bitmagic:*)` would also
|
|
44
|
+
* cover `generate`, `forge` and `cover`, which spend the creator's sparks, and `publish`, which
|
|
45
|
+
* with `--visibility public` lists their game on bitmagic.ai. The permission prompt is currently
|
|
46
|
+
* the only human gate on that spending — AGENTS.md leans on it explicitly ("Generation costs the
|
|
47
|
+
* creator real money. Do not call these in a loop to explore options"), so widening this list
|
|
48
|
+
* removes a safeguard rather than a nuisance. `upgrade` is out too: it replaces `engine/`
|
|
49
|
+
* wholesale, which the creator should see coming.
|
|
50
|
+
*/
|
|
51
|
+
const ALLOWED_COMMANDS = [
|
|
52
|
+
'Bash(bitmagic check:*)',
|
|
53
|
+
'Bash(bitmagic dev:*)',
|
|
54
|
+
'Bash(bitmagic reload:*)',
|
|
55
|
+
'Bash(bitmagic verify:*)',
|
|
56
|
+
'Bash(bitmagic build:*)',
|
|
57
|
+
'Bash(bitmagic whoami:*)',
|
|
58
|
+
];
|
|
59
|
+
/**
|
|
60
|
+
* The settings a project with no `.claude/settings.json` at all gets.
|
|
61
|
+
*
|
|
62
|
+
* Note the asymmetry with `mergeReloadHook` below: an existing settings file gains the Stop hook
|
|
63
|
+
* and nothing else. Permissions are the creator's own security posture, and adding entries to a
|
|
64
|
+
* file they already own is a different kind of act from seeding one that does not exist yet.
|
|
65
|
+
*/
|
|
37
66
|
export function renderClaudeSettings() {
|
|
38
|
-
return stringify({
|
|
67
|
+
return stringify({
|
|
68
|
+
permissions: { allow: ALLOWED_COMMANDS },
|
|
69
|
+
hooks: { Stop: [reloadMatcher()] },
|
|
70
|
+
});
|
|
39
71
|
}
|
|
40
72
|
/**
|
|
41
73
|
* Does this `Stop` group already run our command?
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-settings.js","sourceRoot":"","sources":["../../src/scaffold/claude-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE5D,yFAAyF;AACzF,MAAM,CAAC,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAkBrD,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,SAAS,CAAC,QAAiB;IAClC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC;AAED,8CAA8C;AAC9C,SAAS,aAAa;IACpB,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"claude-settings.js","sourceRoot":"","sources":["../../src/scaffold/claude-settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,13 +15,18 @@ export interface DownloadDeps {
|
|
|
15
15
|
*/
|
|
16
16
|
export declare function downloadAndVerify(tarballUrl: string, expectedSha256: string, targetPath: string, deps: DownloadDeps): Promise<void>;
|
|
17
17
|
/**
|
|
18
|
-
* The tarball stores three prefixes at different depths — game/src/<root>, game/docs (plus
|
|
19
|
-
* sibling game/sw-cache-buster.js, which shares
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* The tarball stores three prefixes at different depths — game/src/<root>, game/agent-docs (plus
|
|
19
|
+
* the sibling game/sw-cache-buster.js, which shares its strip depth) and templates — so it takes
|
|
20
|
+
* three passes. A single pass cannot express them: `strip` is a fixed depth, and stripping 2 would
|
|
21
|
+
* turn game/agent-docs/x.md into a bare x.md.
|
|
22
22
|
*
|
|
23
|
-
* Result: <engineDir>/{engine,types,bundle,debug,editor,genres}, <engineDir>/docs,
|
|
23
|
+
* Result: <engineDir>/{engine,types,bundle,debug,editor,genres}, <engineDir>/agent-docs,
|
|
24
24
|
* <engineDir>/sw-cache-buster.js, and templates unpacked separately so the scaffolder can read
|
|
25
25
|
* templates/index.json.
|
|
26
|
+
*
|
|
27
|
+
* `game/docs` is deliberately absent: those are engine-CONTRIBUTOR docs (how to edit the engine),
|
|
28
|
+
* which is noise in a project where engine/ is read-only. Tarballs published before the corpus
|
|
29
|
+
* moved still carry it, and the second pass simply does not match it — an old tarball produces a
|
|
30
|
+
* project without agent-docs rather than a failure. See OPTIONAL_VENDORED_DIRS in project.ts.
|
|
26
31
|
*/
|
|
27
32
|
export declare function extractEngine(tarballPath: string, engineDir: string, templatesDir: string): Promise<void>;
|
|
@@ -39,14 +39,19 @@ export async function downloadAndVerify(tarballUrl, expectedSha256, targetPath,
|
|
|
39
39
|
fs.writeFileSync(targetPath, body);
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
|
-
* The tarball stores three prefixes at different depths — game/src/<root>, game/docs (plus
|
|
43
|
-
* sibling game/sw-cache-buster.js, which shares
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* The tarball stores three prefixes at different depths — game/src/<root>, game/agent-docs (plus
|
|
43
|
+
* the sibling game/sw-cache-buster.js, which shares its strip depth) and templates — so it takes
|
|
44
|
+
* three passes. A single pass cannot express them: `strip` is a fixed depth, and stripping 2 would
|
|
45
|
+
* turn game/agent-docs/x.md into a bare x.md.
|
|
46
46
|
*
|
|
47
|
-
* Result: <engineDir>/{engine,types,bundle,debug,editor,genres}, <engineDir>/docs,
|
|
47
|
+
* Result: <engineDir>/{engine,types,bundle,debug,editor,genres}, <engineDir>/agent-docs,
|
|
48
48
|
* <engineDir>/sw-cache-buster.js, and templates unpacked separately so the scaffolder can read
|
|
49
49
|
* templates/index.json.
|
|
50
|
+
*
|
|
51
|
+
* `game/docs` is deliberately absent: those are engine-CONTRIBUTOR docs (how to edit the engine),
|
|
52
|
+
* which is noise in a project where engine/ is read-only. Tarballs published before the corpus
|
|
53
|
+
* moved still carry it, and the second pass simply does not match it — an old tarball produces a
|
|
54
|
+
* project without agent-docs rather than a failure. See OPTIONAL_VENDORED_DIRS in project.ts.
|
|
50
55
|
*/
|
|
51
56
|
export async function extractEngine(tarballPath, engineDir, templatesDir) {
|
|
52
57
|
fs.mkdirSync(engineDir, { recursive: true });
|
|
@@ -58,14 +63,15 @@ export async function extractEngine(tarballPath, engineDir, templatesDir) {
|
|
|
58
63
|
strip: 2,
|
|
59
64
|
filter: (entryPath) => entryPath.startsWith('game/src/'),
|
|
60
65
|
});
|
|
61
|
-
// game/docs/
|
|
66
|
+
// game/agent-docs/npc-system.md -> <engineDir>/agent-docs/npc-system.md, and the same for its
|
|
67
|
+
// engine-api/ signature digest and samples/ subdirectories.
|
|
62
68
|
// game/sw-cache-buster.js -> <engineDir>/sw-cache-buster.js (rides this pass because it needs
|
|
63
|
-
// the same strip depth as game/docs/, even though it isn't under
|
|
69
|
+
// the same strip depth as game/agent-docs/, even though it isn't under it)
|
|
64
70
|
await tar.extract({
|
|
65
71
|
file: tarballPath,
|
|
66
72
|
cwd: engineDir,
|
|
67
73
|
strip: 1,
|
|
68
|
-
filter: (entryPath) => entryPath.startsWith('game/docs/') || entryPath === 'game/sw-cache-buster.js',
|
|
74
|
+
filter: (entryPath) => entryPath.startsWith('game/agent-docs/') || entryPath === 'game/sw-cache-buster.js',
|
|
69
75
|
});
|
|
70
76
|
// templates/index.json -> <templatesDir>/index.json
|
|
71
77
|
await tar.extract({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine-download.js","sourceRoot":"","sources":["../../src/scaffold/engine-download.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,MAAM,EAAgB,MAAM,mBAAmB,CAAC;AAczD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,WAAwB,EACxB,WAAmB,EACnB,IAAa;IAEb,OAAO,MAAM,CAAiB,WAAW,EAAE,6BAA6B,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;AAC/F,CAAC;AAMD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAkB,EAClB,cAAsB,EACtB,UAAkB,EAClB,IAAkB;IAElB,IAAI,QAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,QAAQ,CAAC,4DAA4D,CAAC,CAAC;IACnF,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,QAAQ,CAAC,2CAA2C,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IACpF,CAAC;IAED,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,QAAQ,CAChB,0GAA0G,CAC3G,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtE,IAAI,MAAM,KAAK,cAAc,EAAE,CAAC;QAC9B,MAAM,IAAI,QAAQ,CAChB,0EAA0E;YACxE,iEAAiE,CACpE,CAAC;IACJ,CAAC;IAED,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"engine-download.js","sourceRoot":"","sources":["../../src/scaffold/engine-download.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,6 +41,33 @@ export interface ProjectMetadata {
|
|
|
41
41
|
}
|
|
42
42
|
export declare function renderBitmagicJson(metadata: ProjectMetadata): string;
|
|
43
43
|
export declare function renderAgentsMd(): string;
|
|
44
|
+
/**
|
|
45
|
+
* `.claude/skills/planning-a-game/SKILL.md` — the slice contract and the mechanic-recipe routing.
|
|
46
|
+
*
|
|
47
|
+
* These are one skill rather than two on purpose: you route to a recipe *in order to* scope slice
|
|
48
|
+
* 1, and slice 1 is the first section of the plan file the recipe tells you to write. Split, the
|
|
49
|
+
* first instruction of each half would be "now invoke the other half" — and the hosted lane already
|
|
50
|
+
* paid for that hop (see game-play-agent's mechanic-recipe-router.ts: the batch's slowest game read
|
|
51
|
+
* the right recipe twice and still shipped with no plan file, because the plan step sat above the
|
|
52
|
+
* table rather than inside it).
|
|
53
|
+
*
|
|
54
|
+
* They are also deliberately NOT in AGENTS.md, which carries only the trigger and a compressed
|
|
55
|
+
* version. `bitmagic upgrade` refreshes AGENTS.md only while it is byte-identical to what the CLI
|
|
56
|
+
* wrote, but overwrites shipped skills unconditionally — so a creator who has customised their
|
|
57
|
+
* AGENTS.md still receives every word of this.
|
|
58
|
+
*/
|
|
59
|
+
export declare function renderPlanningSkill(): string;
|
|
60
|
+
/**
|
|
61
|
+
* `.claude/skills/finding-engine-apis/SKILL.md` — the discovery protocol in full.
|
|
62
|
+
*
|
|
63
|
+
* The problem this exists to solve is measured, not hypothetical. game-play-agent's
|
|
64
|
+
* engine-api-digest.ts opens with it: session analysis showed the hosted coding agent spending most
|
|
65
|
+
* of its wall-clock reconstructing the engine's public API through dozens of sequential searches,
|
|
66
|
+
* and one digest lookup replaced several code searches. A scaffolded project had the same 240k
|
|
67
|
+
* lines and none of the digest until the corpus started shipping — so an external agent had no
|
|
68
|
+
* option but the expensive one.
|
|
69
|
+
*/
|
|
70
|
+
export declare function renderEngineApiSkill(): string;
|
|
44
71
|
export declare function renderGenerateSkill(): string;
|
|
45
72
|
/**
|
|
46
73
|
* `GAME-DESIGN.md` — the project's design document, and the input `bitmagic cover` builds its
|