@metasession.co/devaudit-cli 0.1.35 → 0.1.36

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
@@ -55,7 +55,7 @@ function emitJsonResult(payload) {
55
55
 
56
56
  // package.json
57
57
  var package_default = {
58
- version: "0.1.35"};
58
+ version: "0.1.36"};
59
59
 
60
60
  // src/lib/version.ts
61
61
  var CLI_VERSION = package_default.version;
@@ -1047,7 +1047,7 @@ async function runAuthProbe(ctx) {
1047
1047
  const client = new DevAuditClient({ token: ctx.token, baseUrl: ctx.baseUrl });
1048
1048
  try {
1049
1049
  await client.listProjects();
1050
- return { step: "1/12 Authenticate", status: "ok", message: `PAT accepted at ${ctx.baseUrl}` };
1050
+ return { step: "1/11 Authenticate", status: "ok", message: `PAT accepted at ${ctx.baseUrl}` };
1051
1051
  } catch (err) {
1052
1052
  if (err instanceof DevAuditApiError && (err.status === 401 || err.status === 403)) {
1053
1053
  throw new Error(
@@ -1102,7 +1102,7 @@ async function detectStack(ctx) {
1102
1102
  }
1103
1103
  function ok(stack, wd) {
1104
1104
  return {
1105
- step: "2/12 Detect stack",
1105
+ step: "2/11 Detect stack",
1106
1106
  status: "ok",
1107
1107
  message: `stack=${stack} working_directory=${wd} host=railway`,
1108
1108
  data: { stack, workingDirectory: wd, host: "railway" }
@@ -1209,7 +1209,7 @@ var PYTHON_PATHS_IGNORE = [
1209
1209
  async function writeSdlcConfig(ctx, plan) {
1210
1210
  if (ctx.installMode === "developer") {
1211
1211
  return {
1212
- step: "4/12 Write sdlc-config.json",
1212
+ step: "4/11 Write sdlc-config.json",
1213
1213
  status: "skipped",
1214
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."
1215
1215
  };
@@ -1263,20 +1263,20 @@ async function writeSdlcConfig(ctx, plan) {
1263
1263
  if (ctx.dryRun) {
1264
1264
  const preserved = existing ? `preserves existing customizations (${Object.keys(existing).filter((k) => !(k in wizardOwned)).length} non-wizard fields)` : "fresh config";
1265
1265
  return {
1266
- step: "4/12 Write sdlc-config.json",
1266
+ step: "4/11 Write sdlc-config.json",
1267
1267
  status: "planned",
1268
1268
  message: `would write ${outPath} (stack=${plan.stack}, slug=${plan.projectSlug}) \u2014 ${preserved}`
1269
1269
  };
1270
1270
  }
1271
1271
  await promises.writeFile(outPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
1272
- return { step: "4/12 Write sdlc-config.json", status: "ok", message: `wrote ${outPath}` };
1272
+ return { step: "4/11 Write sdlc-config.json", status: "ok", message: `wrote ${outPath}` };
1273
1273
  }
1274
1274
 
1275
1275
  // src/install/project.ts
1276
1276
  async function findOrCreateProject(ctx, plan) {
1277
1277
  if (ctx.dryRun) {
1278
1278
  return {
1279
- step: "5/12 Find or create DevAudit project",
1279
+ step: "5/11 Find or create DevAudit project",
1280
1280
  status: "planned",
1281
1281
  message: `would create or find project slug='${plan.projectSlug}' on ${ctx.baseUrl}`
1282
1282
  };
@@ -1286,7 +1286,7 @@ async function findOrCreateProject(ctx, plan) {
1286
1286
  if (existing) {
1287
1287
  plan.projectId = existing.id;
1288
1288
  return {
1289
- step: "5/12 Find or create DevAudit project",
1289
+ step: "5/11 Find or create DevAudit project",
1290
1290
  status: "ok",
1291
1291
  message: `project '${plan.projectSlug}' already exists (id ${existing.id.slice(0, 8)}\u2026) \u2014 skipping creation`,
1292
1292
  data: { projectId: existing.id, created: false }
@@ -1295,7 +1295,7 @@ async function findOrCreateProject(ctx, plan) {
1295
1295
  const created = await client.createProject(plan.projectSlug, plan.projectSlug);
1296
1296
  plan.projectId = created.id;
1297
1297
  return {
1298
- step: "5/12 Find or create DevAudit project",
1298
+ step: "5/11 Find or create DevAudit project",
1299
1299
  status: "ok",
1300
1300
  message: `project '${plan.projectSlug}' created (id ${created.id.slice(0, 8)}\u2026)`,
1301
1301
  data: { projectId: created.id, created: true }
@@ -1307,14 +1307,14 @@ var KEY_NAME = "Onboarding-issued";
1307
1307
  async function issueApiKey(ctx, plan) {
1308
1308
  if (ctx.installMode === "developer") {
1309
1309
  return {
1310
- step: "6/12 Issue project API key",
1310
+ step: "6/11 Issue project API key",
1311
1311
  status: "skipped",
1312
1312
  message: "developer mode \u2014 leaving the project's 'Onboarding-issued' API key untouched (the team key is already configured by the project operator)."
1313
1313
  };
1314
1314
  }
1315
1315
  if (ctx.dryRun) {
1316
1316
  return {
1317
- step: "6/12 Issue project API key",
1317
+ step: "6/11 Issue project API key",
1318
1318
  status: "planned",
1319
1319
  message: `would issue API key named '${KEY_NAME}' on project '${plan.projectSlug}' (if not already present)`
1320
1320
  };
@@ -1327,7 +1327,7 @@ async function issueApiKey(ctx, plan) {
1327
1327
  const live = existing.find((k) => k.name === KEY_NAME && k.revoked_at === null);
1328
1328
  if (live) {
1329
1329
  return {
1330
- step: "6/12 Issue project API key",
1330
+ step: "6/11 Issue project API key",
1331
1331
  status: "warn",
1332
1332
  message: `'${KEY_NAME}' API key already exists \u2014 revoke it in the portal and re-run, or set DEVAUDIT_API_KEY manually`
1333
1333
  };
@@ -1335,7 +1335,7 @@ async function issueApiKey(ctx, plan) {
1335
1335
  const issued = await client.issueApiKey(plan.projectId, KEY_NAME);
1336
1336
  plan.apiKey = issued.plainTextKey;
1337
1337
  return {
1338
- step: "6/12 Issue project API key",
1338
+ step: "6/11 Issue project API key",
1339
1339
  status: "ok",
1340
1340
  message: `issued (will be stored as repo secret DEVAUDIT_API_KEY)`
1341
1341
  };
@@ -1361,7 +1361,7 @@ function buildSkipped(plan) {
1361
1361
  async function setGithubSecrets(ctx, plan, provider) {
1362
1362
  if (ctx.installMode === "developer") {
1363
1363
  return {
1364
- step: "7/12 Set GitHub secrets and variables",
1364
+ step: "7/11 Set GitHub secrets and variables",
1365
1365
  status: "skipped",
1366
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."
1367
1367
  };
@@ -1370,7 +1370,7 @@ async function setGithubSecrets(ctx, plan, provider) {
1370
1370
  if (ctx.dryRun) {
1371
1371
  const summary = operations.map((op) => `${op.kind}:${op.name}`).join(", ");
1372
1372
  return {
1373
- step: "7/12 Set GitHub secrets and variables",
1373
+ step: "7/11 Set GitHub secrets and variables",
1374
1374
  status: "planned",
1375
1375
  message: `would set ${summary} via ${provider.name} provider`
1376
1376
  };
@@ -1384,7 +1384,7 @@ async function setGithubSecrets(ctx, plan, provider) {
1384
1384
  }
1385
1385
  const skipped = buildSkipped(plan);
1386
1386
  const detail = `${operations.length} item(s) set${skipped.length > 0 ? ` (skipped: ${skipped.join("; ")})` : ""}`;
1387
- return { step: "7/12 Set GitHub secrets and variables", status: "ok", message: detail };
1387
+ return { step: "7/11 Set GitHub secrets and variables", status: "ok", message: detail };
1388
1388
  }
1389
1389
  async function commandExists(cmd) {
1390
1390
  try {
@@ -1406,7 +1406,7 @@ async function bootstrapHooks(ctx, plan) {
1406
1406
  if (ctx.dryRun) {
1407
1407
  const action = plan.stack === "python" ? "pre-commit install" : "npx husky init";
1408
1408
  return {
1409
- step: "8/12 Bootstrap hook framework",
1409
+ step: "8/11 Bootstrap hook framework",
1410
1410
  status: "planned",
1411
1411
  message: `would run \`${action}\` in ${ctx.projectPath}`
1412
1412
  };
@@ -1417,29 +1417,29 @@ async function bootstrapHooks(ctx, plan) {
1417
1417
  async function bootstrapPython(ctx) {
1418
1418
  if (!await commandExists("pre-commit")) {
1419
1419
  return {
1420
- step: "8/12 Bootstrap hook framework",
1420
+ step: "8/11 Bootstrap hook framework",
1421
1421
  status: "warn",
1422
1422
  message: "pre-commit not on PATH \u2014 run `pip install pre-commit && pre-commit install` manually"
1423
1423
  };
1424
1424
  }
1425
1425
  await execa("pre-commit", ["install"], { cwd: ctx.projectPath, stdio: "inherit" });
1426
1426
  await execa("pre-commit", ["install", "--hook-type", "commit-msg"], { cwd: ctx.projectPath, stdio: "inherit" });
1427
- return { step: "8/12 Bootstrap hook framework", status: "ok", message: "pre-commit hooks installed" };
1427
+ return { step: "8/11 Bootstrap hook framework", status: "ok", message: "pre-commit hooks installed" };
1428
1428
  }
1429
1429
  async function bootstrapNode(ctx) {
1430
1430
  const huskyDir = join(ctx.projectPath, ".husky");
1431
1431
  if (await dirExists(huskyDir)) {
1432
- return { step: "8/12 Bootstrap hook framework", status: "ok", message: ".husky/ already exists" };
1432
+ return { step: "8/11 Bootstrap hook framework", status: "ok", message: ".husky/ already exists" };
1433
1433
  }
1434
1434
  if (!await commandExists("npx")) {
1435
1435
  return {
1436
- step: "8/12 Bootstrap hook framework",
1436
+ step: "8/11 Bootstrap hook framework",
1437
1437
  status: "warn",
1438
1438
  message: "npx not on PATH \u2014 run `npx husky init` manually"
1439
1439
  };
1440
1440
  }
1441
1441
  await execa("npx", ["husky", "init"], { cwd: ctx.projectPath, stdio: "inherit" });
1442
- return { step: "8/12 Bootstrap hook framework", status: "ok", message: ".husky/ bootstrapped" };
1442
+ return { step: "8/11 Bootstrap hook framework", status: "ok", message: ".husky/ bootstrapped" };
1443
1443
  }
1444
1444
 
1445
1445
  // src/install/branch-protection.ts
@@ -1451,7 +1451,7 @@ var REQUIRED_CHECKS = [
1451
1451
  async function configureBranchProtection(ctx, provider) {
1452
1452
  if (ctx.installMode === "developer") {
1453
1453
  return {
1454
- step: "9/12 Configure branch protection",
1454
+ step: "9/11 Configure branch protection",
1455
1455
  status: "skipped",
1456
1456
  message: "developer mode \u2014 leaving branch protection unchanged. Use --force-team-config to re-apply as the project operator."
1457
1457
  };
@@ -1461,7 +1461,7 @@ async function configureBranchProtection(ctx, provider) {
1461
1461
  meta = await provider.getRepoMeta(ctx.projectPath);
1462
1462
  } catch (err) {
1463
1463
  return {
1464
- step: "9/12 Configure branch protection",
1464
+ step: "9/11 Configure branch protection",
1465
1465
  status: "warn",
1466
1466
  message: `could not resolve git repo (${err.message}) \u2014 configure manually`
1467
1467
  };
@@ -1469,7 +1469,7 @@ async function configureBranchProtection(ctx, provider) {
1469
1469
  const repo = `${meta.owner}/${meta.name}`;
1470
1470
  if (ctx.dryRun) {
1471
1471
  return {
1472
- step: "9/12 Configure branch protection",
1472
+ step: "9/11 Configure branch protection",
1473
1473
  status: "planned",
1474
1474
  message: `would apply branch protection on ${repo}:${meta.defaultBranch} with checks=${JSON.stringify(REQUIRED_CHECKS)}`
1475
1475
  };
@@ -1477,13 +1477,13 @@ async function configureBranchProtection(ctx, provider) {
1477
1477
  const result = await provider.applyBranchProtection(ctx.projectPath, meta.defaultBranch, REQUIRED_CHECKS);
1478
1478
  if (result.applied) {
1479
1479
  return {
1480
- step: "9/12 Configure branch protection",
1480
+ step: "9/11 Configure branch protection",
1481
1481
  status: "ok",
1482
1482
  message: `required checks on ${meta.defaultBranch}: ${REQUIRED_CHECKS.join(", ")}`
1483
1483
  };
1484
1484
  }
1485
1485
  return {
1486
- step: "9/12 Configure branch protection",
1486
+ step: "9/11 Configure branch protection",
1487
1487
  status: "warn",
1488
1488
  message: `${result.message ?? "branch-protection apply failed"} \u2014 configure manually`
1489
1489
  };
@@ -2145,68 +2145,19 @@ async function syncAll(projectPaths) {
2145
2145
  async function syncTemplates(ctx) {
2146
2146
  if (ctx.dryRun) {
2147
2147
  return {
2148
- step: "10/12 Sync SDLC templates",
2148
+ step: "10/11 Sync SDLC templates",
2149
2149
  status: "planned",
2150
2150
  message: `would run native syncProject() against ${ctx.projectPath}`
2151
2151
  };
2152
2152
  }
2153
2153
  const report = await syncProject(ctx.projectPath);
2154
2154
  return {
2155
- step: "10/12 Sync SDLC templates",
2155
+ step: "10/11 Sync SDLC templates",
2156
2156
  status: "ok",
2157
2157
  message: `synced ${report.totalFilesSynced} files across ${report.sections.length} sections`,
2158
2158
  data: { totalFilesSynced: report.totalFilesSynced }
2159
2159
  };
2160
2160
  }
2161
- var STEP = "11/12 Bootstrap governance docs";
2162
- var SOURCE_REL = "sdlc/files/_common/governance";
2163
- var TARGET_REL = "compliance/governance";
2164
- async function bootstrapGovernanceDocs(ctx) {
2165
- const sourceDir = resolve(ctx.installerRoot, SOURCE_REL);
2166
- const targetDir = resolve(ctx.projectPath, TARGET_REL);
2167
- let templates = [];
2168
- try {
2169
- templates = (await readdir(sourceDir)).filter((name) => name.endsWith(".md.template")).sort();
2170
- } catch (err) {
2171
- return {
2172
- step: STEP,
2173
- status: "warn",
2174
- message: `source directory not found: ${sourceDir} (${err.message})`
2175
- };
2176
- }
2177
- if (templates.length === 0) {
2178
- return { step: STEP, status: "warn", message: `no .md.template files in ${sourceDir}` };
2179
- }
2180
- if (ctx.dryRun) {
2181
- return {
2182
- step: STEP,
2183
- status: "planned",
2184
- message: `would copy ${templates.length} starter(s) to ${TARGET_REL}/ (skip-if-exists)`,
2185
- data: { templates: [...templates] }
2186
- };
2187
- }
2188
- await ensureDir(targetDir);
2189
- const copied = [];
2190
- const skipped = [];
2191
- for (const template of templates) {
2192
- const targetName = template.replace(/\.template$/, "");
2193
- const targetPath = join(targetDir, targetName);
2194
- if (await isFile(targetPath)) {
2195
- skipped.push(targetName);
2196
- continue;
2197
- }
2198
- const body = await readFile(join(sourceDir, template), "utf8");
2199
- await writeFile(targetPath, body, "utf8");
2200
- copied.push(targetName);
2201
- }
2202
- const detail = skipped.length === 0 ? `${copied.length} starter(s) copied to ${TARGET_REL}/` : `${copied.length} copied, ${skipped.length} kept (already on disk)`;
2203
- return {
2204
- step: STEP,
2205
- status: "ok",
2206
- message: `${detail} \u2014 STARTERS, edit before production (see docs/governance-templates.md)`,
2207
- data: { copied, skipped, targetDir: TARGET_REL }
2208
- };
2209
- }
2210
2161
 
2211
2162
  // src/install/done-report.ts
2212
2163
  function doneReport(ctx, plan) {
@@ -2235,7 +2186,7 @@ function doneReport(ctx, plan) {
2235
2186
  ""
2236
2187
  ];
2237
2188
  return {
2238
- step: "12/12 Done (developer mode)",
2189
+ step: "11/11 Done (developer mode)",
2239
2190
  status: "ok",
2240
2191
  message: lines2.join("\n"),
2241
2192
  data: { mode: "developer" }
@@ -2262,7 +2213,7 @@ function doneReport(ctx, plan) {
2262
2213
  ""
2263
2214
  ];
2264
2215
  return {
2265
- step: "12/12 Done",
2216
+ step: "11/11 Done",
2266
2217
  status: "ok",
2267
2218
  message: lines.join("\n"),
2268
2219
  data: { nextBranch: branch, mode: "operator" }
@@ -2314,7 +2265,7 @@ async function runInstall(options) {
2314
2265
  steps.push(await record(log, setGithubSecrets(ctx, plan, providerResolution.provider)));
2315
2266
  } else {
2316
2267
  const skipped = {
2317
- step: "7/12 Set GitHub secrets and variables",
2268
+ step: "7/11 Set GitHub secrets and variables",
2318
2269
  status: "skipped",
2319
2270
  message: providerResolution.reason ?? "no git provider available"
2320
2271
  };
@@ -2326,7 +2277,7 @@ async function runInstall(options) {
2326
2277
  steps.push(await record(log, configureBranchProtection(ctx, providerResolution.provider)));
2327
2278
  } else {
2328
2279
  const skipped = {
2329
- step: "9/12 Configure branch protection",
2280
+ step: "9/11 Configure branch protection",
2330
2281
  status: "skipped",
2331
2282
  message: providerResolution.reason ?? "no git provider available"
2332
2283
  };
@@ -2334,7 +2285,6 @@ async function runInstall(options) {
2334
2285
  log.warn(`[${skipped.step}] SKIPPED ${skipped.message}`);
2335
2286
  }
2336
2287
  steps.push(await record(log, syncTemplates(ctx)));
2337
- steps.push(await record(log, bootstrapGovernanceDocs(ctx)));
2338
2288
  const done = doneReport(ctx, plan);
2339
2289
  steps.push(done);
2340
2290
  log.success(`[${done.step}]`);
@@ -2430,7 +2380,7 @@ async function record(log, p) {
2430
2380
  }
2431
2381
  function planSummary(plan) {
2432
2382
  return {
2433
- step: "3/12 Configure",
2383
+ step: "3/11 Configure",
2434
2384
  status: "ok",
2435
2385
  message: `slug=${plan.projectSlug} runtime=${plan.runtimeVersion}`,
2436
2386
  data: { ...plan }
@@ -2522,6 +2472,80 @@ async function runUpdate(options) {
2522
2472
  log.log("");
2523
2473
  log.warn("Do NOT auto-commit \u2014 review the changes first.");
2524
2474
  }
2475
+ var STEP = "Bootstrap governance docs";
2476
+ var SOURCE_REL = "sdlc/files/_common/governance";
2477
+ var TARGET_REL = "compliance/governance";
2478
+ async function bootstrapGovernanceDocs(ctx) {
2479
+ const sourceDir = resolve(ctx.installerRoot, SOURCE_REL);
2480
+ const targetDir = resolve(ctx.projectPath, TARGET_REL);
2481
+ let templates = [];
2482
+ try {
2483
+ templates = (await readdir(sourceDir)).filter((name) => name.endsWith(".md.template")).sort();
2484
+ } catch (err) {
2485
+ return {
2486
+ step: STEP,
2487
+ status: "warn",
2488
+ message: `source directory not found: ${sourceDir} (${err.message})`
2489
+ };
2490
+ }
2491
+ if (templates.length === 0) {
2492
+ return { step: STEP, status: "warn", message: `no .md.template files in ${sourceDir}` };
2493
+ }
2494
+ if (ctx.dryRun) {
2495
+ return {
2496
+ step: STEP,
2497
+ status: "planned",
2498
+ message: `would copy ${templates.length} starter(s) to ${TARGET_REL}/ (skip-if-exists)`,
2499
+ data: { templates: [...templates] }
2500
+ };
2501
+ }
2502
+ await ensureDir(targetDir);
2503
+ const copied = [];
2504
+ const skipped = [];
2505
+ for (const template of templates) {
2506
+ const targetName = template.replace(/\.template$/, "");
2507
+ const targetPath = join(targetDir, targetName);
2508
+ if (await isFile(targetPath)) {
2509
+ skipped.push(targetName);
2510
+ continue;
2511
+ }
2512
+ const body = await readFile(join(sourceDir, template), "utf8");
2513
+ await writeFile(targetPath, body, "utf8");
2514
+ copied.push(targetName);
2515
+ }
2516
+ const detail = skipped.length === 0 ? `${copied.length} starter(s) copied to ${TARGET_REL}/` : `${copied.length} copied, ${skipped.length} kept (already on disk)`;
2517
+ return {
2518
+ step: STEP,
2519
+ status: "ok",
2520
+ message: `${detail} \u2014 STARTERS, edit before production (see docs/governance-templates.md)`,
2521
+ data: { copied, skipped, targetDir: TARGET_REL }
2522
+ };
2523
+ }
2524
+
2525
+ // src/commands/bootstrap-governance.ts
2526
+ async function runBootstrapGovernance(opts) {
2527
+ const projectPath = resolve(opts.path ?? process.cwd());
2528
+ const installerRoot = await resolveInstallerRoot();
2529
+ const log = logger();
2530
+ const result = await bootstrapGovernanceDocs({
2531
+ projectPath,
2532
+ installerRoot,
2533
+ dryRun: Boolean(opts.dryRun)
2534
+ });
2535
+ if (isJsonMode()) {
2536
+ emitJsonResult(result);
2537
+ return;
2538
+ }
2539
+ if (result.status === "ok") {
2540
+ log.success(`[${result.step}] ${result.message ?? ""}`);
2541
+ } else if (result.status === "warn") {
2542
+ log.warn(`[${result.step}] ${result.message ?? ""}`);
2543
+ } else if (result.status === "planned") {
2544
+ log.info(`[${result.step}] (dry-run) ${result.message ?? ""}`);
2545
+ } else {
2546
+ log.log(`[${result.step}] ${result.message ?? ""}`);
2547
+ }
2548
+ }
2525
2549
 
2526
2550
  // src/commands/stub.ts
2527
2551
  function makeStub(info) {
@@ -2781,6 +2805,12 @@ async function main(argv) {
2781
2805
  });
2782
2806
  }
2783
2807
  );
2808
+ program.command("bootstrap-governance [path]").description(
2809
+ "Copy governance starter templates (ropa, dpia, ai-disclosure, periodic-review, incident-report) into compliance/governance/. Opt-in since v0.1.36 \u2014 auto-seed during install was removed because the placeholders auto-uploaded as evidence on first CI push."
2810
+ ).action(async (path, _opts, cmd) => {
2811
+ const opts = cmd?.optsWithGlobals() ?? {};
2812
+ await runBootstrapGovernance({ path, dryRun: opts.dryRun });
2813
+ });
2784
2814
  program.command("doctor").description("Verify the local install: required tools on PATH, auth state, config validity").action(runDoctor);
2785
2815
  program.command("status [path]").description("Show the consumer project's framework state").action(async (path) => {
2786
2816
  await runStatus({ path });