@stackbilt/cli 0.3.2 → 0.4.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 +18 -5
- package/dist/commands/adf-bundle.d.ts +8 -0
- package/dist/commands/adf-bundle.d.ts.map +1 -0
- package/dist/commands/adf-bundle.js +169 -0
- package/dist/commands/adf-bundle.js.map +1 -0
- package/dist/commands/adf-evidence.d.ts +8 -0
- package/dist/commands/adf-evidence.d.ts.map +1 -0
- package/dist/commands/adf-evidence.js +273 -0
- package/dist/commands/adf-evidence.js.map +1 -0
- package/dist/commands/adf-migrate.d.ts +10 -0
- package/dist/commands/adf-migrate.d.ts.map +1 -0
- package/dist/commands/adf-migrate.js +383 -0
- package/dist/commands/adf-migrate.js.map +1 -0
- package/dist/commands/adf-sync.d.ts +10 -0
- package/dist/commands/adf-sync.d.ts.map +1 -0
- package/dist/commands/adf-sync.js +213 -0
- package/dist/commands/adf-sync.js.map +1 -0
- package/dist/commands/adf.d.ts +7 -1
- package/dist/commands/adf.d.ts.map +1 -1
- package/dist/commands/adf.js +109 -472
- package/dist/commands/adf.js.map +1 -1
- package/dist/commands/bootstrap.d.ts +9 -0
- package/dist/commands/bootstrap.d.ts.map +1 -0
- package/dist/commands/bootstrap.js +660 -0
- package/dist/commands/bootstrap.js.map +1 -0
- package/dist/commands/hook.d.ts.map +1 -1
- package/dist/commands/hook.js +69 -10
- package/dist/commands/hook.js.map +1 -1
- package/dist/commands/setup.d.ts +103 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +7 -0
- package/dist/commands/setup.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* charter bootstrap
|
|
4
|
+
*
|
|
5
|
+
* One-command repo onboarding: detect + setup + ADF init + install + doctor.
|
|
6
|
+
* Replaces the multi-step manual process with a single orchestrated flow.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.bootstrapCommand = bootstrapCommand;
|
|
43
|
+
const fs = __importStar(require("node:fs"));
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
const crypto = __importStar(require("node:crypto"));
|
|
46
|
+
const node_child_process_1 = require("node:child_process");
|
|
47
|
+
const index_1 = require("../index");
|
|
48
|
+
const init_1 = require("./init");
|
|
49
|
+
const setup_1 = require("./setup");
|
|
50
|
+
const adf_1 = require("./adf");
|
|
51
|
+
const config_1 = require("../config");
|
|
52
|
+
const adf_2 = require("@stackbilt/adf");
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Command Entry
|
|
55
|
+
// ============================================================================
|
|
56
|
+
async function bootstrapCommand(options, args) {
|
|
57
|
+
const ciTarget = getFlag(args, '--ci');
|
|
58
|
+
const presetFlag = getFlag(args, '--preset');
|
|
59
|
+
const skipInstall = args.includes('--skip-install');
|
|
60
|
+
const skipDoctor = args.includes('--skip-doctor');
|
|
61
|
+
const force = options.yes;
|
|
62
|
+
if (ciTarget && ciTarget !== 'github') {
|
|
63
|
+
throw new index_1.CLIError(`Unsupported CI target: ${ciTarget}. Supported: github`);
|
|
64
|
+
}
|
|
65
|
+
if (presetFlag && !isValidPreset(presetFlag)) {
|
|
66
|
+
throw new index_1.CLIError(`Invalid --preset value: ${presetFlag}. Use worker|frontend|backend|fullstack.`);
|
|
67
|
+
}
|
|
68
|
+
const result = {
|
|
69
|
+
status: 'success',
|
|
70
|
+
steps: [],
|
|
71
|
+
nextSteps: [],
|
|
72
|
+
};
|
|
73
|
+
let warnings = 0;
|
|
74
|
+
// ========================================================================
|
|
75
|
+
// Phase 1: Detect
|
|
76
|
+
// ========================================================================
|
|
77
|
+
const detectResult = runDetectPhase(options, presetFlag);
|
|
78
|
+
result.steps.push(detectResult.step);
|
|
79
|
+
if (detectResult.step.status === 'fail')
|
|
80
|
+
warnings++;
|
|
81
|
+
const selectedPreset = detectResult.selectedPreset;
|
|
82
|
+
const detection = detectResult.detection;
|
|
83
|
+
const contexts = detectResult.contexts;
|
|
84
|
+
const packageManager = detectResult.packageManager;
|
|
85
|
+
if (options.format === 'text') {
|
|
86
|
+
console.log('[1/5] Detecting stack...');
|
|
87
|
+
console.log(` Stack: ${selectedPreset} (${detection.confidence} confidence)`);
|
|
88
|
+
console.log(` Monorepo: ${detection.monorepo ? 'yes' : 'no'}${detection.monorepo && detection.signals.hasPnpm ? ' (pnpm workspace)' : ''}`);
|
|
89
|
+
if (detection.warnings.length > 0) {
|
|
90
|
+
for (const w of detection.warnings) {
|
|
91
|
+
console.log(` Warning: ${w}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
console.log('');
|
|
95
|
+
}
|
|
96
|
+
// ========================================================================
|
|
97
|
+
// Phase 2: Setup
|
|
98
|
+
// ========================================================================
|
|
99
|
+
const setupResult = runSetupPhase(options, selectedPreset, detection, contexts, ciTarget, packageManager, force);
|
|
100
|
+
result.steps.push(setupResult.step);
|
|
101
|
+
if (setupResult.step.status === 'fail')
|
|
102
|
+
warnings++;
|
|
103
|
+
if (options.format === 'text') {
|
|
104
|
+
console.log('[2/5] Setting up governance...');
|
|
105
|
+
for (const f of (setupResult.step.details.created || [])) {
|
|
106
|
+
console.log(` Created ${f}`);
|
|
107
|
+
}
|
|
108
|
+
for (const f of (setupResult.step.details.updated || [])) {
|
|
109
|
+
console.log(` Updated ${f}`);
|
|
110
|
+
}
|
|
111
|
+
console.log('');
|
|
112
|
+
}
|
|
113
|
+
// ========================================================================
|
|
114
|
+
// Phase 3: ADF Init
|
|
115
|
+
// ========================================================================
|
|
116
|
+
const adfResult = runAdfInitPhase(options, force);
|
|
117
|
+
result.steps.push(adfResult.step);
|
|
118
|
+
if (adfResult.step.status === 'fail')
|
|
119
|
+
warnings++;
|
|
120
|
+
if (options.format === 'text') {
|
|
121
|
+
console.log('[3/5] Initializing ADF context...');
|
|
122
|
+
for (const f of (adfResult.step.details.files || [])) {
|
|
123
|
+
console.log(` Created ${f}`);
|
|
124
|
+
}
|
|
125
|
+
for (const f of (adfResult.step.details.pointers || [])) {
|
|
126
|
+
console.log(` Generated ${f}`);
|
|
127
|
+
}
|
|
128
|
+
console.log('');
|
|
129
|
+
}
|
|
130
|
+
// ========================================================================
|
|
131
|
+
// Phase 4: Install
|
|
132
|
+
// ========================================================================
|
|
133
|
+
const installResult = runInstallPhase(options, skipInstall);
|
|
134
|
+
result.steps.push(installResult.step);
|
|
135
|
+
if (installResult.step.status === 'fail')
|
|
136
|
+
warnings++;
|
|
137
|
+
if (options.format === 'text') {
|
|
138
|
+
console.log('[4/5] Installing dependencies...');
|
|
139
|
+
if (skipInstall) {
|
|
140
|
+
console.log(' Skipped (--skip-install)');
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
console.log(` Detected: ${installResult.step.details.packageManager}`);
|
|
144
|
+
console.log(` Running: ${installResult.step.details.command}`);
|
|
145
|
+
if (installResult.step.status === 'pass') {
|
|
146
|
+
console.log(' Done');
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.log(` Failed: ${installResult.step.details.error}`);
|
|
150
|
+
console.log(' (non-fatal: run install manually)');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
console.log('');
|
|
154
|
+
}
|
|
155
|
+
// ========================================================================
|
|
156
|
+
// Phase 5: Doctor
|
|
157
|
+
// ========================================================================
|
|
158
|
+
const doctorResult = runDoctorPhase(options, skipDoctor);
|
|
159
|
+
result.steps.push(doctorResult.step);
|
|
160
|
+
if (doctorResult.step.status === 'fail')
|
|
161
|
+
warnings++;
|
|
162
|
+
if (options.format === 'text') {
|
|
163
|
+
console.log('[5/5] Running health check...');
|
|
164
|
+
if (skipDoctor) {
|
|
165
|
+
console.log(' Skipped (--skip-doctor)');
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const checks = doctorResult.step.details.checks || [];
|
|
169
|
+
for (const check of checks) {
|
|
170
|
+
const icon = check.status === 'PASS' ? '[ok]' : '[warn]';
|
|
171
|
+
console.log(` ${icon} ${check.name}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
console.log('');
|
|
175
|
+
}
|
|
176
|
+
// ========================================================================
|
|
177
|
+
// Summary
|
|
178
|
+
// ========================================================================
|
|
179
|
+
const failCount = result.steps.filter(s => s.status === 'fail').length;
|
|
180
|
+
result.status = failCount === 0 ? 'success' : failCount < result.steps.length ? 'partial' : 'failure';
|
|
181
|
+
// Build next steps
|
|
182
|
+
result.nextSteps.push({
|
|
183
|
+
cmd: 'Review .charter/patterns/ and customize for your stack',
|
|
184
|
+
required: false,
|
|
185
|
+
reason: 'Customize blessed stack patterns',
|
|
186
|
+
});
|
|
187
|
+
result.nextSteps.push({
|
|
188
|
+
cmd: 'Add project-specific rules to .ai/core.adf',
|
|
189
|
+
required: false,
|
|
190
|
+
reason: 'Add project-specific ADF rules',
|
|
191
|
+
});
|
|
192
|
+
result.nextSteps.push({
|
|
193
|
+
cmd: 'git add .charter .ai CLAUDE.md .cursorrules agents.md && git commit -m "chore: bootstrap charter governance"',
|
|
194
|
+
required: false,
|
|
195
|
+
reason: 'Commit governance baseline',
|
|
196
|
+
});
|
|
197
|
+
if (options.format === 'json') {
|
|
198
|
+
console.log(JSON.stringify(result, null, 2));
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
console.log(`Bootstrap complete. ${warnings} warning${warnings === 1 ? '' : 's'}.`);
|
|
202
|
+
console.log('');
|
|
203
|
+
console.log('Next steps:');
|
|
204
|
+
result.nextSteps.forEach((step, i) => {
|
|
205
|
+
console.log(` ${i + 1}. ${step.cmd}`);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return index_1.EXIT_CODE.SUCCESS;
|
|
209
|
+
}
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// Phase 1: Detect
|
|
212
|
+
// ============================================================================
|
|
213
|
+
function runDetectPhase(_options, presetFlag) {
|
|
214
|
+
const warnings = [];
|
|
215
|
+
try {
|
|
216
|
+
const contexts = (0, setup_1.loadPackageContexts)();
|
|
217
|
+
const detection = (0, setup_1.detectStack)(contexts);
|
|
218
|
+
const packageManager = (0, setup_1.detectPackageManager)(contexts);
|
|
219
|
+
const selectedPreset = isValidPreset(presetFlag) ? presetFlag : detection.suggestedPreset;
|
|
220
|
+
warnings.push(...detection.warnings);
|
|
221
|
+
return {
|
|
222
|
+
step: {
|
|
223
|
+
name: 'detect',
|
|
224
|
+
status: 'pass',
|
|
225
|
+
details: {
|
|
226
|
+
stack: selectedPreset,
|
|
227
|
+
confidence: detection.confidence,
|
|
228
|
+
monorepo: detection.monorepo,
|
|
229
|
+
runtime: detection.runtime,
|
|
230
|
+
frameworks: detection.frameworks,
|
|
231
|
+
},
|
|
232
|
+
warnings,
|
|
233
|
+
},
|
|
234
|
+
selectedPreset,
|
|
235
|
+
detection,
|
|
236
|
+
contexts,
|
|
237
|
+
packageManager,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
242
|
+
warnings.push(`Detection failed: ${msg}`);
|
|
243
|
+
const emptyContexts = loadPackageContextsSafe();
|
|
244
|
+
return {
|
|
245
|
+
step: {
|
|
246
|
+
name: 'detect',
|
|
247
|
+
status: 'fail',
|
|
248
|
+
details: { error: msg },
|
|
249
|
+
warnings,
|
|
250
|
+
},
|
|
251
|
+
selectedPreset: isValidPreset(presetFlag) ? presetFlag : 'fullstack',
|
|
252
|
+
detection: {
|
|
253
|
+
runtime: [],
|
|
254
|
+
frameworks: [],
|
|
255
|
+
state: [],
|
|
256
|
+
sources: [],
|
|
257
|
+
agentStandards: [],
|
|
258
|
+
monorepo: false,
|
|
259
|
+
signals: {
|
|
260
|
+
hasFrontend: false,
|
|
261
|
+
hasBackend: false,
|
|
262
|
+
hasWorker: false,
|
|
263
|
+
hasCloudflare: false,
|
|
264
|
+
hasHono: false,
|
|
265
|
+
hasReact: false,
|
|
266
|
+
hasVite: false,
|
|
267
|
+
hasPnpm: false,
|
|
268
|
+
},
|
|
269
|
+
mixedStack: false,
|
|
270
|
+
confidence: 'LOW',
|
|
271
|
+
suggestedPreset: 'fullstack',
|
|
272
|
+
warnings: [],
|
|
273
|
+
},
|
|
274
|
+
contexts: emptyContexts,
|
|
275
|
+
packageManager: 'npm',
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function loadPackageContextsSafe() {
|
|
280
|
+
try {
|
|
281
|
+
return (0, setup_1.loadPackageContexts)();
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// Phase 2: Setup
|
|
289
|
+
// ============================================================================
|
|
290
|
+
function runSetupPhase(options, selectedPreset, detection, contexts, ciTarget, packageManager, force) {
|
|
291
|
+
const warnings = [];
|
|
292
|
+
const created = [];
|
|
293
|
+
const updated = [];
|
|
294
|
+
try {
|
|
295
|
+
// Initialize .charter/ directory
|
|
296
|
+
const initResult = (0, init_1.initializeCharter)(options.configPath, force, {
|
|
297
|
+
preset: selectedPreset,
|
|
298
|
+
projectName: (0, setup_1.inferProjectName)(contexts),
|
|
299
|
+
features: {
|
|
300
|
+
cloudflare: detection.signals.hasCloudflare,
|
|
301
|
+
hono: detection.signals.hasHono,
|
|
302
|
+
react: detection.signals.hasReact,
|
|
303
|
+
vite: detection.signals.hasVite,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
if (initResult.created) {
|
|
307
|
+
for (const f of initResult.files) {
|
|
308
|
+
created.push(path.join(options.configPath, f));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Generate CI workflow if requested
|
|
312
|
+
if (ciTarget === 'github') {
|
|
313
|
+
const workflowPath = path.join('.github', 'workflows', 'charter-governance.yml');
|
|
314
|
+
const workflowWrite = (0, setup_1.applyManagedFile)(workflowPath, (0, setup_1.getGithubWorkflow)(packageManager), true);
|
|
315
|
+
if (workflowWrite.created) {
|
|
316
|
+
created.push(workflowPath);
|
|
317
|
+
}
|
|
318
|
+
else if (workflowWrite.updated) {
|
|
319
|
+
updated.push(workflowPath);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Sync package.json scripts + devDependency
|
|
323
|
+
const manifestApplied = (0, setup_1.syncPackageManifest)(selectedPreset, true, true);
|
|
324
|
+
if (manifestApplied.legacy.scripts.updated) {
|
|
325
|
+
updated.push('package.json (scripts)');
|
|
326
|
+
}
|
|
327
|
+
if (manifestApplied.legacy.dependencies.updated) {
|
|
328
|
+
updated.push('package.json (devDependencies)');
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
step: {
|
|
332
|
+
name: 'setup',
|
|
333
|
+
status: 'pass',
|
|
334
|
+
details: { created, updated },
|
|
335
|
+
warnings,
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
341
|
+
warnings.push(`Setup failed: ${msg}`);
|
|
342
|
+
return {
|
|
343
|
+
step: {
|
|
344
|
+
name: 'setup',
|
|
345
|
+
status: 'fail',
|
|
346
|
+
details: { created, updated, error: msg },
|
|
347
|
+
warnings,
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// ============================================================================
|
|
353
|
+
// Phase 3: ADF Init
|
|
354
|
+
// ============================================================================
|
|
355
|
+
function runAdfInitPhase(options, force) {
|
|
356
|
+
const warnings = [];
|
|
357
|
+
const files = [];
|
|
358
|
+
const pointers = [];
|
|
359
|
+
try {
|
|
360
|
+
const aiDir = '.ai';
|
|
361
|
+
const manifestPath = path.join(aiDir, 'manifest.adf');
|
|
362
|
+
// Create .ai/ scaffolds
|
|
363
|
+
const alreadyExists = fs.existsSync(manifestPath);
|
|
364
|
+
const hasCustomContent = alreadyExists && hasCustomAdfContent(aiDir);
|
|
365
|
+
if (!alreadyExists) {
|
|
366
|
+
// Greenfield: write scaffolds
|
|
367
|
+
fs.mkdirSync(aiDir, { recursive: true });
|
|
368
|
+
fs.writeFileSync(path.join(aiDir, 'manifest.adf'), adf_1.MANIFEST_SCAFFOLD);
|
|
369
|
+
fs.writeFileSync(path.join(aiDir, 'core.adf'), adf_1.CORE_SCAFFOLD);
|
|
370
|
+
fs.writeFileSync(path.join(aiDir, 'state.adf'), adf_1.STATE_SCAFFOLD);
|
|
371
|
+
files.push('.ai/manifest.adf', '.ai/core.adf', '.ai/state.adf');
|
|
372
|
+
// Write .adf.lock
|
|
373
|
+
const lockData = {};
|
|
374
|
+
for (const mod of ['core.adf', 'state.adf']) {
|
|
375
|
+
const content = fs.readFileSync(path.join(aiDir, mod), 'utf-8');
|
|
376
|
+
lockData[mod] = hashContent(content);
|
|
377
|
+
}
|
|
378
|
+
fs.writeFileSync(path.join(aiDir, '.adf.lock'), JSON.stringify(lockData, null, 2) + '\n');
|
|
379
|
+
files.push('.ai/.adf.lock');
|
|
380
|
+
}
|
|
381
|
+
else if (hasCustomContent && !force) {
|
|
382
|
+
// Custom ADF content exists — don't overwrite, suggest migrate
|
|
383
|
+
warnings.push('.ai/ contains custom ADF content; skipping scaffold overwrite');
|
|
384
|
+
warnings.push("Run 'charter adf migrate' to consolidate agent configs into ADF");
|
|
385
|
+
}
|
|
386
|
+
else if (force) {
|
|
387
|
+
// Force overwrite
|
|
388
|
+
fs.mkdirSync(aiDir, { recursive: true });
|
|
389
|
+
fs.writeFileSync(path.join(aiDir, 'manifest.adf'), adf_1.MANIFEST_SCAFFOLD);
|
|
390
|
+
fs.writeFileSync(path.join(aiDir, 'core.adf'), adf_1.CORE_SCAFFOLD);
|
|
391
|
+
fs.writeFileSync(path.join(aiDir, 'state.adf'), adf_1.STATE_SCAFFOLD);
|
|
392
|
+
files.push('.ai/manifest.adf', '.ai/core.adf', '.ai/state.adf');
|
|
393
|
+
const lockData = {};
|
|
394
|
+
for (const mod of ['core.adf', 'state.adf']) {
|
|
395
|
+
const content = fs.readFileSync(path.join(aiDir, mod), 'utf-8');
|
|
396
|
+
lockData[mod] = hashContent(content);
|
|
397
|
+
}
|
|
398
|
+
fs.writeFileSync(path.join(aiDir, '.adf.lock'), JSON.stringify(lockData, null, 2) + '\n');
|
|
399
|
+
files.push('.ai/.adf.lock');
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
warnings.push('.ai/ already exists; skipping scaffold (use --yes to overwrite)');
|
|
403
|
+
}
|
|
404
|
+
// Generate thin pointer files
|
|
405
|
+
const pointerFiles = [
|
|
406
|
+
{ name: 'CLAUDE.md', content: adf_1.POINTER_CLAUDE_MD, label: 'CLAUDE.md (thin pointer)' },
|
|
407
|
+
{ name: '.cursorrules', content: adf_1.POINTER_CURSORRULES, label: '.cursorrules (thin pointer)' },
|
|
408
|
+
{ name: 'agents.md', content: adf_1.POINTER_AGENTS_MD, label: 'agents.md (thin pointer)' },
|
|
409
|
+
];
|
|
410
|
+
for (const pf of pointerFiles) {
|
|
411
|
+
const pointerPath = path.resolve(pf.name);
|
|
412
|
+
const exists = fs.existsSync(pointerPath);
|
|
413
|
+
if (!exists) {
|
|
414
|
+
fs.writeFileSync(pointerPath, pf.content);
|
|
415
|
+
pointers.push(pf.label);
|
|
416
|
+
}
|
|
417
|
+
else if (exists && !isAlreadyThinPointer(pointerPath)) {
|
|
418
|
+
// File has custom content — don't overwrite, suggest migrate
|
|
419
|
+
warnings.push(`${pf.name} has custom content; skipping pointer (use 'charter adf migrate' first)`);
|
|
420
|
+
}
|
|
421
|
+
else if (force) {
|
|
422
|
+
fs.writeFileSync(pointerPath, pf.content);
|
|
423
|
+
pointers.push(pf.label);
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
warnings.push(`${pf.name} already exists; skipping (use --yes to overwrite)`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
step: {
|
|
431
|
+
name: 'adf-init',
|
|
432
|
+
status: 'pass',
|
|
433
|
+
details: { files, pointers },
|
|
434
|
+
warnings,
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
440
|
+
warnings.push(`ADF init failed: ${msg}`);
|
|
441
|
+
return {
|
|
442
|
+
step: {
|
|
443
|
+
name: 'adf-init',
|
|
444
|
+
status: 'fail',
|
|
445
|
+
details: { files, pointers, error: msg },
|
|
446
|
+
warnings,
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// ============================================================================
|
|
452
|
+
// Phase 4: Install
|
|
453
|
+
// ============================================================================
|
|
454
|
+
function runInstallPhase(_options, skipInstall) {
|
|
455
|
+
const warnings = [];
|
|
456
|
+
if (skipInstall) {
|
|
457
|
+
return {
|
|
458
|
+
step: {
|
|
459
|
+
name: 'install',
|
|
460
|
+
status: 'skip',
|
|
461
|
+
details: { skipped: true, reason: '--skip-install' },
|
|
462
|
+
warnings,
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
// Detect package manager from lockfiles
|
|
467
|
+
const pm = detectPackageManagerFromLockfiles();
|
|
468
|
+
const command = `${pm} install`;
|
|
469
|
+
try {
|
|
470
|
+
(0, node_child_process_1.execSync)(command, {
|
|
471
|
+
stdio: 'pipe',
|
|
472
|
+
env: { ...process.env, CI: 'true' },
|
|
473
|
+
timeout: 120_000,
|
|
474
|
+
});
|
|
475
|
+
return {
|
|
476
|
+
step: {
|
|
477
|
+
name: 'install',
|
|
478
|
+
status: 'pass',
|
|
479
|
+
details: { packageManager: pm, command },
|
|
480
|
+
warnings,
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
catch (err) {
|
|
485
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
486
|
+
warnings.push(`Install failed: ${msg}`);
|
|
487
|
+
return {
|
|
488
|
+
step: {
|
|
489
|
+
name: 'install',
|
|
490
|
+
status: 'fail',
|
|
491
|
+
details: { packageManager: pm, command, error: msg },
|
|
492
|
+
warnings,
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function detectPackageManagerFromLockfiles() {
|
|
498
|
+
if (fs.existsSync(path.resolve('pnpm-lock.yaml')))
|
|
499
|
+
return 'pnpm';
|
|
500
|
+
if (fs.existsSync(path.resolve('yarn.lock')))
|
|
501
|
+
return 'yarn';
|
|
502
|
+
if (fs.existsSync(path.resolve('package-lock.json')))
|
|
503
|
+
return 'npm';
|
|
504
|
+
return 'npm';
|
|
505
|
+
}
|
|
506
|
+
// ============================================================================
|
|
507
|
+
// Phase 5: Doctor
|
|
508
|
+
// ============================================================================
|
|
509
|
+
function runDoctorPhase(options, skipDoctor) {
|
|
510
|
+
const warnings = [];
|
|
511
|
+
if (skipDoctor) {
|
|
512
|
+
return {
|
|
513
|
+
step: {
|
|
514
|
+
name: 'doctor',
|
|
515
|
+
status: 'skip',
|
|
516
|
+
details: { skipped: true, reason: '--skip-doctor' },
|
|
517
|
+
warnings,
|
|
518
|
+
},
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
const checks = [];
|
|
522
|
+
try {
|
|
523
|
+
// Git repository check
|
|
524
|
+
let inGitRepo = false;
|
|
525
|
+
try {
|
|
526
|
+
(0, node_child_process_1.execSync)('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
|
|
527
|
+
inGitRepo = true;
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
// not in a git repo
|
|
531
|
+
}
|
|
532
|
+
checks.push({
|
|
533
|
+
name: 'Git repository',
|
|
534
|
+
status: inGitRepo ? 'PASS' : 'WARN',
|
|
535
|
+
details: inGitRepo ? 'Repository detected.' : 'Not inside a git repository.',
|
|
536
|
+
});
|
|
537
|
+
// config.json check
|
|
538
|
+
const configFile = path.join(options.configPath, 'config.json');
|
|
539
|
+
const hasConfig = fs.existsSync(configFile);
|
|
540
|
+
checks.push({
|
|
541
|
+
name: 'config.json',
|
|
542
|
+
status: hasConfig ? 'PASS' : 'WARN',
|
|
543
|
+
details: hasConfig ? `${configFile} exists.` : `${configFile} not found.`,
|
|
544
|
+
});
|
|
545
|
+
// Patterns check
|
|
546
|
+
const patterns = (0, config_1.loadPatterns)(options.configPath);
|
|
547
|
+
checks.push({
|
|
548
|
+
name: 'Patterns loaded',
|
|
549
|
+
status: patterns.length > 0 ? 'PASS' : 'WARN',
|
|
550
|
+
details: patterns.length > 0
|
|
551
|
+
? `${patterns.length} pattern(s) loaded.`
|
|
552
|
+
: 'No patterns found.',
|
|
553
|
+
});
|
|
554
|
+
// ADF manifest check
|
|
555
|
+
const manifestPath = path.join('.ai', 'manifest.adf');
|
|
556
|
+
const hasManifest = fs.existsSync(manifestPath);
|
|
557
|
+
checks.push({
|
|
558
|
+
name: 'ADF manifest',
|
|
559
|
+
status: hasManifest ? 'PASS' : 'WARN',
|
|
560
|
+
details: hasManifest ? `${manifestPath} exists.` : `${manifestPath} not found.`,
|
|
561
|
+
});
|
|
562
|
+
// ADF sync lock check
|
|
563
|
+
if (hasManifest) {
|
|
564
|
+
try {
|
|
565
|
+
const manifestContent = fs.readFileSync(manifestPath, 'utf-8');
|
|
566
|
+
const manifestDoc = (0, adf_2.parseAdf)(manifestContent);
|
|
567
|
+
const manifest = (0, adf_2.parseManifest)(manifestDoc);
|
|
568
|
+
if (manifest.sync.length > 0) {
|
|
569
|
+
const lockFile = path.join('.ai', '.adf.lock');
|
|
570
|
+
const hasLock = fs.existsSync(lockFile);
|
|
571
|
+
checks.push({
|
|
572
|
+
name: 'ADF sync lock',
|
|
573
|
+
status: hasLock ? 'PASS' : 'WARN',
|
|
574
|
+
details: hasLock ? `${lockFile} exists.` : `${lockFile} not found.`,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
// Manifest parse failed — just check for lock file presence
|
|
580
|
+
const lockFile = path.join('.ai', '.adf.lock');
|
|
581
|
+
const hasLock = fs.existsSync(lockFile);
|
|
582
|
+
checks.push({
|
|
583
|
+
name: 'ADF sync lock',
|
|
584
|
+
status: hasLock ? 'PASS' : 'WARN',
|
|
585
|
+
details: hasLock ? `${lockFile} exists.` : `${lockFile} not found.`,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const hasWarn = checks.some(c => c.status === 'WARN');
|
|
590
|
+
if (hasWarn) {
|
|
591
|
+
warnings.push('Some health checks returned warnings.');
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
step: {
|
|
595
|
+
name: 'doctor',
|
|
596
|
+
status: hasWarn ? 'fail' : 'pass',
|
|
597
|
+
details: { checks },
|
|
598
|
+
warnings,
|
|
599
|
+
},
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
catch (err) {
|
|
603
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
604
|
+
warnings.push(`Doctor failed: ${msg}`);
|
|
605
|
+
return {
|
|
606
|
+
step: {
|
|
607
|
+
name: 'doctor',
|
|
608
|
+
status: 'fail',
|
|
609
|
+
details: { checks, error: msg },
|
|
610
|
+
warnings,
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
// ============================================================================
|
|
616
|
+
// Helpers
|
|
617
|
+
// ============================================================================
|
|
618
|
+
function getFlag(args, flag) {
|
|
619
|
+
const idx = args.indexOf(flag);
|
|
620
|
+
if (idx !== -1 && idx + 1 < args.length) {
|
|
621
|
+
return args[idx + 1];
|
|
622
|
+
}
|
|
623
|
+
return undefined;
|
|
624
|
+
}
|
|
625
|
+
function isValidPreset(value) {
|
|
626
|
+
return value === 'worker' || value === 'frontend' || value === 'backend' || value === 'fullstack';
|
|
627
|
+
}
|
|
628
|
+
function hashContent(content) {
|
|
629
|
+
return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Check if .ai/core.adf has content beyond the scaffold template.
|
|
633
|
+
*/
|
|
634
|
+
function hasCustomAdfContent(aiDir) {
|
|
635
|
+
const coreAdfPath = path.join(aiDir, 'core.adf');
|
|
636
|
+
if (!fs.existsSync(coreAdfPath))
|
|
637
|
+
return false;
|
|
638
|
+
try {
|
|
639
|
+
const content = fs.readFileSync(coreAdfPath, 'utf-8');
|
|
640
|
+
// Check if the file has been modified from default scaffold
|
|
641
|
+
// A custom file will have different content than the CORE_SCAFFOLD
|
|
642
|
+
return content.trim() !== adf_1.CORE_SCAFFOLD.trim();
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Check if a file is already a thin ADF pointer.
|
|
650
|
+
*/
|
|
651
|
+
function isAlreadyThinPointer(filePath) {
|
|
652
|
+
try {
|
|
653
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
654
|
+
return content.includes('Do not duplicate ADF rules here');
|
|
655
|
+
}
|
|
656
|
+
catch {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
//# sourceMappingURL=bootstrap.js.map
|