@metasession.co/devaudit-cli 0.1.29 → 0.1.30

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/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import envPaths from 'env-paths';
8
8
  import { fileURLToPath, pathToFileURL } from 'url';
9
9
  import { validateManifest } from '@metasession.co/devaudit-plugin-sdk';
10
10
  import * as clack2 from '@clack/prompts';
11
+ import { readdir, readFile, writeFile } from 'fs/promises';
11
12
 
12
13
  var DEFAULT_OPTIONS = { json: false, verbose: false, noColor: false };
13
14
  var currentLogger = build(DEFAULT_OPTIONS);
@@ -54,7 +55,7 @@ function emitJsonResult(payload) {
54
55
 
55
56
  // package.json
56
57
  var package_default = {
57
- version: "0.1.29"};
58
+ version: "0.1.30"};
58
59
 
59
60
  // src/lib/version.ts
60
61
  var CLI_VERSION = package_default.version;
@@ -1046,7 +1047,7 @@ async function runAuthProbe(ctx) {
1046
1047
  const client = new DevAuditClient({ token: ctx.token, baseUrl: ctx.baseUrl });
1047
1048
  try {
1048
1049
  await client.listProjects();
1049
- return { step: "1/11 Authenticate", status: "ok", message: `PAT accepted at ${ctx.baseUrl}` };
1050
+ return { step: "1/12 Authenticate", status: "ok", message: `PAT accepted at ${ctx.baseUrl}` };
1050
1051
  } catch (err) {
1051
1052
  if (err instanceof DevAuditApiError && (err.status === 401 || err.status === 403)) {
1052
1053
  throw new Error(
@@ -1101,7 +1102,7 @@ async function detectStack(ctx) {
1101
1102
  }
1102
1103
  function ok(stack, wd) {
1103
1104
  return {
1104
- step: "2/11 Detect stack",
1105
+ step: "2/12 Detect stack",
1105
1106
  status: "ok",
1106
1107
  message: `stack=${stack} working_directory=${wd} host=railway`,
1107
1108
  data: { stack, workingDirectory: wd, host: "railway" }
@@ -1208,7 +1209,7 @@ var PYTHON_PATHS_IGNORE = [
1208
1209
  async function writeSdlcConfig(ctx, plan) {
1209
1210
  if (ctx.installMode === "developer") {
1210
1211
  return {
1211
- step: "4/11 Write sdlc-config.json",
1212
+ step: "4/12 Write sdlc-config.json",
1212
1213
  status: "skipped",
1213
1214
  message: "developer mode \u2014 leaving sdlc-config.json untouched (the team config is already on disk from the project operator). Use --force-team-config if you need to refresh wizard-owned fields."
1214
1215
  };
@@ -1262,20 +1263,20 @@ async function writeSdlcConfig(ctx, plan) {
1262
1263
  if (ctx.dryRun) {
1263
1264
  const preserved = existing ? `preserves existing customizations (${Object.keys(existing).filter((k) => !(k in wizardOwned)).length} non-wizard fields)` : "fresh config";
1264
1265
  return {
1265
- step: "4/11 Write sdlc-config.json",
1266
+ step: "4/12 Write sdlc-config.json",
1266
1267
  status: "planned",
1267
1268
  message: `would write ${outPath} (stack=${plan.stack}, slug=${plan.projectSlug}) \u2014 ${preserved}`
1268
1269
  };
1269
1270
  }
1270
1271
  await promises.writeFile(outPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
1271
- return { step: "4/11 Write sdlc-config.json", status: "ok", message: `wrote ${outPath}` };
1272
+ return { step: "4/12 Write sdlc-config.json", status: "ok", message: `wrote ${outPath}` };
1272
1273
  }
1273
1274
 
1274
1275
  // src/install/project.ts
1275
1276
  async function findOrCreateProject(ctx, plan) {
1276
1277
  if (ctx.dryRun) {
1277
1278
  return {
1278
- step: "5/11 Find or create DevAudit project",
1279
+ step: "5/12 Find or create DevAudit project",
1279
1280
  status: "planned",
1280
1281
  message: `would create or find project slug='${plan.projectSlug}' on ${ctx.baseUrl}`
1281
1282
  };
@@ -1285,7 +1286,7 @@ async function findOrCreateProject(ctx, plan) {
1285
1286
  if (existing) {
1286
1287
  plan.projectId = existing.id;
1287
1288
  return {
1288
- step: "5/11 Find or create DevAudit project",
1289
+ step: "5/12 Find or create DevAudit project",
1289
1290
  status: "ok",
1290
1291
  message: `project '${plan.projectSlug}' already exists (id ${existing.id.slice(0, 8)}\u2026) \u2014 skipping creation`,
1291
1292
  data: { projectId: existing.id, created: false }
@@ -1294,7 +1295,7 @@ async function findOrCreateProject(ctx, plan) {
1294
1295
  const created = await client.createProject(plan.projectSlug, plan.projectSlug);
1295
1296
  plan.projectId = created.id;
1296
1297
  return {
1297
- step: "5/11 Find or create DevAudit project",
1298
+ step: "5/12 Find or create DevAudit project",
1298
1299
  status: "ok",
1299
1300
  message: `project '${plan.projectSlug}' created (id ${created.id.slice(0, 8)}\u2026)`,
1300
1301
  data: { projectId: created.id, created: true }
@@ -1306,14 +1307,14 @@ var KEY_NAME = "Onboarding-issued";
1306
1307
  async function issueApiKey(ctx, plan) {
1307
1308
  if (ctx.installMode === "developer") {
1308
1309
  return {
1309
- step: "6/11 Issue project API key",
1310
+ step: "6/12 Issue project API key",
1310
1311
  status: "skipped",
1311
1312
  message: "developer mode \u2014 leaving the project's 'Onboarding-issued' API key untouched (the team key is already configured by the project operator)."
1312
1313
  };
1313
1314
  }
1314
1315
  if (ctx.dryRun) {
1315
1316
  return {
1316
- step: "6/11 Issue project API key",
1317
+ step: "6/12 Issue project API key",
1317
1318
  status: "planned",
1318
1319
  message: `would issue API key named '${KEY_NAME}' on project '${plan.projectSlug}' (if not already present)`
1319
1320
  };
@@ -1326,7 +1327,7 @@ async function issueApiKey(ctx, plan) {
1326
1327
  const live = existing.find((k) => k.name === KEY_NAME && k.revoked_at === null);
1327
1328
  if (live) {
1328
1329
  return {
1329
- step: "6/11 Issue project API key",
1330
+ step: "6/12 Issue project API key",
1330
1331
  status: "warn",
1331
1332
  message: `'${KEY_NAME}' API key already exists \u2014 revoke it in the portal and re-run, or set DEVAUDIT_API_KEY manually`
1332
1333
  };
@@ -1334,7 +1335,7 @@ async function issueApiKey(ctx, plan) {
1334
1335
  const issued = await client.issueApiKey(plan.projectId, KEY_NAME);
1335
1336
  plan.apiKey = issued.plainTextKey;
1336
1337
  return {
1337
- step: "6/11 Issue project API key",
1338
+ step: "6/12 Issue project API key",
1338
1339
  status: "ok",
1339
1340
  message: `issued (will be stored as repo secret DEVAUDIT_API_KEY)`
1340
1341
  };
@@ -1360,7 +1361,7 @@ function buildSkipped(plan) {
1360
1361
  async function setGithubSecrets(ctx, plan, provider) {
1361
1362
  if (ctx.installMode === "developer") {
1362
1363
  return {
1363
- step: "7/11 Set GitHub secrets and variables",
1364
+ step: "7/12 Set GitHub secrets and variables",
1364
1365
  status: "skipped",
1365
1366
  message: "developer mode \u2014 leaving DEVAUDIT_USER_TOKEN, DEVAUDIT_API_KEY, DEVAUDIT_BASE_URL, and the production-URL secret unchanged. Use --force-team-config to rotate them as the project operator."
1366
1367
  };
@@ -1369,7 +1370,7 @@ async function setGithubSecrets(ctx, plan, provider) {
1369
1370
  if (ctx.dryRun) {
1370
1371
  const summary = operations.map((op) => `${op.kind}:${op.name}`).join(", ");
1371
1372
  return {
1372
- step: "7/11 Set GitHub secrets and variables",
1373
+ step: "7/12 Set GitHub secrets and variables",
1373
1374
  status: "planned",
1374
1375
  message: `would set ${summary} via ${provider.name} provider`
1375
1376
  };
@@ -1383,7 +1384,7 @@ async function setGithubSecrets(ctx, plan, provider) {
1383
1384
  }
1384
1385
  const skipped = buildSkipped(plan);
1385
1386
  const detail = `${operations.length} item(s) set${skipped.length > 0 ? ` (skipped: ${skipped.join("; ")})` : ""}`;
1386
- return { step: "7/11 Set GitHub secrets and variables", status: "ok", message: detail };
1387
+ return { step: "7/12 Set GitHub secrets and variables", status: "ok", message: detail };
1387
1388
  }
1388
1389
  async function commandExists(cmd) {
1389
1390
  try {
@@ -1405,7 +1406,7 @@ async function bootstrapHooks(ctx, plan) {
1405
1406
  if (ctx.dryRun) {
1406
1407
  const action = plan.stack === "python" ? "pre-commit install" : "npx husky init";
1407
1408
  return {
1408
- step: "8/11 Bootstrap hook framework",
1409
+ step: "8/12 Bootstrap hook framework",
1409
1410
  status: "planned",
1410
1411
  message: `would run \`${action}\` in ${ctx.projectPath}`
1411
1412
  };
@@ -1416,29 +1417,29 @@ async function bootstrapHooks(ctx, plan) {
1416
1417
  async function bootstrapPython(ctx) {
1417
1418
  if (!await commandExists("pre-commit")) {
1418
1419
  return {
1419
- step: "8/11 Bootstrap hook framework",
1420
+ step: "8/12 Bootstrap hook framework",
1420
1421
  status: "warn",
1421
1422
  message: "pre-commit not on PATH \u2014 run `pip install pre-commit && pre-commit install` manually"
1422
1423
  };
1423
1424
  }
1424
1425
  await execa("pre-commit", ["install"], { cwd: ctx.projectPath, stdio: "inherit" });
1425
1426
  await execa("pre-commit", ["install", "--hook-type", "commit-msg"], { cwd: ctx.projectPath, stdio: "inherit" });
1426
- return { step: "8/11 Bootstrap hook framework", status: "ok", message: "pre-commit hooks installed" };
1427
+ return { step: "8/12 Bootstrap hook framework", status: "ok", message: "pre-commit hooks installed" };
1427
1428
  }
1428
1429
  async function bootstrapNode(ctx) {
1429
1430
  const huskyDir = join(ctx.projectPath, ".husky");
1430
1431
  if (await dirExists(huskyDir)) {
1431
- return { step: "8/11 Bootstrap hook framework", status: "ok", message: ".husky/ already exists" };
1432
+ return { step: "8/12 Bootstrap hook framework", status: "ok", message: ".husky/ already exists" };
1432
1433
  }
1433
1434
  if (!await commandExists("npx")) {
1434
1435
  return {
1435
- step: "8/11 Bootstrap hook framework",
1436
+ step: "8/12 Bootstrap hook framework",
1436
1437
  status: "warn",
1437
1438
  message: "npx not on PATH \u2014 run `npx husky init` manually"
1438
1439
  };
1439
1440
  }
1440
1441
  await execa("npx", ["husky", "init"], { cwd: ctx.projectPath, stdio: "inherit" });
1441
- return { step: "8/11 Bootstrap hook framework", status: "ok", message: ".husky/ bootstrapped" };
1442
+ return { step: "8/12 Bootstrap hook framework", status: "ok", message: ".husky/ bootstrapped" };
1442
1443
  }
1443
1444
 
1444
1445
  // src/install/branch-protection.ts
@@ -1450,7 +1451,7 @@ var REQUIRED_CHECKS = [
1450
1451
  async function configureBranchProtection(ctx, provider) {
1451
1452
  if (ctx.installMode === "developer") {
1452
1453
  return {
1453
- step: "9/11 Configure branch protection",
1454
+ step: "9/12 Configure branch protection",
1454
1455
  status: "skipped",
1455
1456
  message: "developer mode \u2014 leaving branch protection unchanged. Use --force-team-config to re-apply as the project operator."
1456
1457
  };
@@ -1460,7 +1461,7 @@ async function configureBranchProtection(ctx, provider) {
1460
1461
  meta = await provider.getRepoMeta(ctx.projectPath);
1461
1462
  } catch (err) {
1462
1463
  return {
1463
- step: "9/11 Configure branch protection",
1464
+ step: "9/12 Configure branch protection",
1464
1465
  status: "warn",
1465
1466
  message: `could not resolve git repo (${err.message}) \u2014 configure manually`
1466
1467
  };
@@ -1468,7 +1469,7 @@ async function configureBranchProtection(ctx, provider) {
1468
1469
  const repo = `${meta.owner}/${meta.name}`;
1469
1470
  if (ctx.dryRun) {
1470
1471
  return {
1471
- step: "9/11 Configure branch protection",
1472
+ step: "9/12 Configure branch protection",
1472
1473
  status: "planned",
1473
1474
  message: `would apply branch protection on ${repo}:${meta.defaultBranch} with checks=${JSON.stringify(REQUIRED_CHECKS)}`
1474
1475
  };
@@ -1476,13 +1477,13 @@ async function configureBranchProtection(ctx, provider) {
1476
1477
  const result = await provider.applyBranchProtection(ctx.projectPath, meta.defaultBranch, REQUIRED_CHECKS);
1477
1478
  if (result.applied) {
1478
1479
  return {
1479
- step: "9/11 Configure branch protection",
1480
+ step: "9/12 Configure branch protection",
1480
1481
  status: "ok",
1481
1482
  message: `required checks on ${meta.defaultBranch}: ${REQUIRED_CHECKS.join(", ")}`
1482
1483
  };
1483
1484
  }
1484
1485
  return {
1485
- step: "9/11 Configure branch protection",
1486
+ step: "9/12 Configure branch protection",
1486
1487
  status: "warn",
1487
1488
  message: `${result.message ?? "branch-protection apply failed"} \u2014 configure manually`
1488
1489
  };
@@ -2121,19 +2122,68 @@ async function syncAll(projectPaths) {
2121
2122
  async function syncTemplates(ctx) {
2122
2123
  if (ctx.dryRun) {
2123
2124
  return {
2124
- step: "10/11 Sync SDLC templates",
2125
+ step: "10/12 Sync SDLC templates",
2125
2126
  status: "planned",
2126
2127
  message: `would run native syncProject() against ${ctx.projectPath}`
2127
2128
  };
2128
2129
  }
2129
2130
  const report = await syncProject(ctx.projectPath);
2130
2131
  return {
2131
- step: "10/11 Sync SDLC templates",
2132
+ step: "10/12 Sync SDLC templates",
2132
2133
  status: "ok",
2133
2134
  message: `synced ${report.totalFilesSynced} files across ${report.sections.length} sections`,
2134
2135
  data: { totalFilesSynced: report.totalFilesSynced }
2135
2136
  };
2136
2137
  }
2138
+ var STEP = "11/12 Bootstrap governance docs";
2139
+ var SOURCE_REL = "sdlc/files/_common/governance";
2140
+ var TARGET_REL = "compliance/governance";
2141
+ async function bootstrapGovernanceDocs(ctx) {
2142
+ const sourceDir = resolve(ctx.installerRoot, SOURCE_REL);
2143
+ const targetDir = resolve(ctx.projectPath, TARGET_REL);
2144
+ let templates = [];
2145
+ try {
2146
+ templates = (await readdir(sourceDir)).filter((name) => name.endsWith(".md.template")).sort();
2147
+ } catch (err) {
2148
+ return {
2149
+ step: STEP,
2150
+ status: "warn",
2151
+ message: `source directory not found: ${sourceDir} (${err.message})`
2152
+ };
2153
+ }
2154
+ if (templates.length === 0) {
2155
+ return { step: STEP, status: "warn", message: `no .md.template files in ${sourceDir}` };
2156
+ }
2157
+ if (ctx.dryRun) {
2158
+ return {
2159
+ step: STEP,
2160
+ status: "planned",
2161
+ message: `would copy ${templates.length} starter(s) to ${TARGET_REL}/ (skip-if-exists)`,
2162
+ data: { templates: [...templates] }
2163
+ };
2164
+ }
2165
+ await ensureDir(targetDir);
2166
+ const copied = [];
2167
+ const skipped = [];
2168
+ for (const template of templates) {
2169
+ const targetName = template.replace(/\.template$/, "");
2170
+ const targetPath = join(targetDir, targetName);
2171
+ if (await isFile(targetPath)) {
2172
+ skipped.push(targetName);
2173
+ continue;
2174
+ }
2175
+ const body = await readFile(join(sourceDir, template), "utf8");
2176
+ await writeFile(targetPath, body, "utf8");
2177
+ copied.push(targetName);
2178
+ }
2179
+ const detail = skipped.length === 0 ? `${copied.length} starter(s) copied to ${TARGET_REL}/` : `${copied.length} copied, ${skipped.length} kept (already on disk)`;
2180
+ return {
2181
+ step: STEP,
2182
+ status: "ok",
2183
+ message: `${detail} \u2014 STARTERS, edit before production (see docs/governance-templates.md)`,
2184
+ data: { copied, skipped, targetDir: TARGET_REL }
2185
+ };
2186
+ }
2137
2187
 
2138
2188
  // src/install/done-report.ts
2139
2189
  function doneReport(ctx, plan) {
@@ -2162,7 +2212,7 @@ function doneReport(ctx, plan) {
2162
2212
  ""
2163
2213
  ];
2164
2214
  return {
2165
- step: "11/11 Done (developer mode)",
2215
+ step: "12/12 Done (developer mode)",
2166
2216
  status: "ok",
2167
2217
  message: lines2.join("\n"),
2168
2218
  data: { mode: "developer" }
@@ -2189,7 +2239,7 @@ function doneReport(ctx, plan) {
2189
2239
  ""
2190
2240
  ];
2191
2241
  return {
2192
- step: "11/11 Done",
2242
+ step: "12/12 Done",
2193
2243
  status: "ok",
2194
2244
  message: lines.join("\n"),
2195
2245
  data: { nextBranch: branch, mode: "operator" }
@@ -2241,7 +2291,7 @@ async function runInstall(options) {
2241
2291
  steps.push(await record(log, setGithubSecrets(ctx, plan, providerResolution.provider)));
2242
2292
  } else {
2243
2293
  const skipped = {
2244
- step: "7/11 Set GitHub secrets and variables",
2294
+ step: "7/12 Set GitHub secrets and variables",
2245
2295
  status: "skipped",
2246
2296
  message: providerResolution.reason ?? "no git provider available"
2247
2297
  };
@@ -2253,7 +2303,7 @@ async function runInstall(options) {
2253
2303
  steps.push(await record(log, configureBranchProtection(ctx, providerResolution.provider)));
2254
2304
  } else {
2255
2305
  const skipped = {
2256
- step: "9/11 Configure branch protection",
2306
+ step: "9/12 Configure branch protection",
2257
2307
  status: "skipped",
2258
2308
  message: providerResolution.reason ?? "no git provider available"
2259
2309
  };
@@ -2261,6 +2311,7 @@ async function runInstall(options) {
2261
2311
  log.warn(`[${skipped.step}] SKIPPED ${skipped.message}`);
2262
2312
  }
2263
2313
  steps.push(await record(log, syncTemplates(ctx)));
2314
+ steps.push(await record(log, bootstrapGovernanceDocs(ctx)));
2264
2315
  const done = doneReport(ctx, plan);
2265
2316
  steps.push(done);
2266
2317
  log.success(`[${done.step}]`);
@@ -2356,7 +2407,7 @@ async function record(log, p) {
2356
2407
  }
2357
2408
  function planSummary(plan) {
2358
2409
  return {
2359
- step: "3/11 Configure",
2410
+ step: "3/12 Configure",
2360
2411
  status: "ok",
2361
2412
  message: `slug=${plan.projectSlug} runtime=${plan.runtimeVersion}`,
2362
2413
  data: { ...plan }