@archpilotlabs/archpilot 0.0.12 → 0.0.13

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.
Files changed (2) hide show
  1. package/dist/archpilot-cli.js +747 -15
  2. package/package.json +1 -1
@@ -215113,6 +215113,16 @@ async function writeFileIfMissing(filePath, contents) {
215113
215113
  await import_node_fs7.promises.writeFile(filePath, contents, { encoding: "utf8" });
215114
215114
  return true;
215115
215115
  }
215116
+ function labelTenantModel(model) {
215117
+ switch (model) {
215118
+ case "single_tenant":
215119
+ return "Single tenant (one tenant per deployment)";
215120
+ case "same_db_same_schema":
215121
+ return "Multi-tenant: same DB, same schema (tenant_id discriminator)";
215122
+ case "same_db_different_schema":
215123
+ return "Multi-tenant: same DB, different schema per tenant";
215124
+ }
215125
+ }
215116
215126
  function defaultArchitectureContractSections(config) {
215117
215127
  const apiEnabled = config?.apiStyle !== "none";
215118
215128
  const databaseEnabled = config?.database !== "none";
@@ -215420,6 +215430,157 @@ function buildDefaultModuleContractPath(moduleName, contract) {
215420
215430
  const safeModuleName = moduleName.replace(/\\/g, "/").split("/").map((segment) => segment.trim()).filter((segment) => segment.length > 0).join(".");
215421
215431
  return `${contractsRoot}/${safeModuleName}.contract.json`;
215422
215432
  }
215433
+ function buildDefaultModulePublicEntrypoints(moduleName, contract) {
215434
+ const modulesRoot = resolveModulesRootFromContract(contract);
215435
+ const adapter = resolvePrimaryAdapterFromContract(contract);
215436
+ const moduleIndexFileName = adapter.scaffoldingDefaults?.moduleIndexFileName ?? "index.ts";
215437
+ const entrypoints = [`${modulesRoot}/${moduleName}/${moduleIndexFileName}`];
215438
+ const publicDirectoryName = adapter.publicEntrypointDirectoryNames[0];
215439
+ if (publicDirectoryName && publicDirectoryName.trim().length > 0) {
215440
+ entrypoints.push(`${modulesRoot}/${moduleName}/${publicDirectoryName}`);
215441
+ }
215442
+ return entrypoints;
215443
+ }
215444
+ function overviewTemplate(config) {
215445
+ const stackSummary = [
215446
+ config.stacks.backendStack ? `backend=${config.stacks.backendStack}` : void 0,
215447
+ config.stacks.frontendStack ? `frontend=${config.stacks.frontendStack}` : void 0,
215448
+ config.stacks.libraryStack ? `library=${config.stacks.libraryStack}` : void 0
215449
+ ].filter((entry) => typeof entry === "string").join(", ");
215450
+ return `# Architecture Overview
215451
+
215452
+ ## Summary
215453
+ - **Project**: ${config.projectName}
215454
+ - **Repository topology**: ${config.repoTopology}
215455
+ - **Project kinds**: ${config.projectKinds.join(", ")}
215456
+ - **Stacks**: ${stackSummary.length > 0 ? stackSummary : "not specified"}
215457
+ - **Architecture style**: ${config.architectureStyle}
215458
+ - **API style**: ${config.apiStyle}
215459
+ - **Auth style**: ${config.authStyle}
215460
+ - **Database**: ${config.database}
215461
+ - **Tenant model**: ${config.tenantModel} - ${labelTenantModel(config.tenantModel)}
215462
+
215463
+ ## Implementation notes
215464
+ - This repository was initialized by **ArchPilot** in offline mode.
215465
+ - Edit \`.archpilot/architecture.json\` to reflect future decisions.
215466
+
215467
+ ## Next steps
215468
+ - Add service/module boundaries (packages or folders) consistent with **${config.architectureStyle}**.
215469
+ - Expand the API contract in \`contracts/openapi.yaml\`.
215470
+ - Add additional ADRs as decisions are made.
215471
+ `;
215472
+ }
215473
+ function adrTenantModelTemplate(config) {
215474
+ const decision = labelTenantModel(config.tenantModel);
215475
+ const schemaNotes = config.tenantModel === "same_db_different_schema" ? "- Each tenant maps to a dedicated schema.\n- Cross-tenant access is prevented via schema isolation." : config.tenantModel === "same_db_same_schema" ? "- Shared schema with a required `tenant_id` discriminator on tenant-scoped tables.\n- Access control must filter by `tenant_id` at all data access boundaries." : "- Tenant isolation is achieved by separate deployments/environments.\n- Data separation is handled operationally rather than in-schema.";
215476
+ return `# ADR-001: Tenant model
215477
+
215478
+ Status: Accepted
215479
+
215480
+ ## Context
215481
+ ${config.projectName} needs a tenant strategy that matches the product and operational model.
215482
+
215483
+ Chosen inputs:
215484
+ - **Tenant model**: ${config.tenantModel}
215485
+ - **Database**: ${config.database}
215486
+ - **Architecture style**: ${config.architectureStyle}
215487
+
215488
+ ## Decision
215489
+ We will use: **${decision}**.
215490
+
215491
+ ## Consequences
215492
+ ${schemaNotes}
215493
+
215494
+ - We will document tenant boundaries in endpoints, data model, and operational runbooks.
215495
+ - We will revisit this decision if scaling, compliance, or customer isolation requirements change.
215496
+
215497
+ ## Alternatives Considered
215498
+ Optional.
215499
+
215500
+ ## Migration Plan
215501
+ Optional.
215502
+
215503
+ ## References
215504
+ Optional.
215505
+ `;
215506
+ }
215507
+ function openApiTemplate(config) {
215508
+ const paths = config.apiStyle === "graphql" ? ` /graphql:
215509
+ post:
215510
+ summary: GraphQL endpoint
215511
+ requestBody:
215512
+ required: true
215513
+ content:
215514
+ application/json:
215515
+ schema:
215516
+ type: object
215517
+ properties:
215518
+ query:
215519
+ type: string
215520
+ responses:
215521
+ '200':
215522
+ description: OK
215523
+ ` : ` /health:
215524
+ get:
215525
+ summary: Health check
215526
+ responses:
215527
+ '200':
215528
+ description: OK
215529
+ `;
215530
+ const securitySchemes = config.authStyle === "jwt" ? ` bearerAuth:
215531
+ type: http
215532
+ scheme: bearer
215533
+ bearerFormat: JWT
215534
+ ` : ` cookieAuth:
215535
+ type: apiKey
215536
+ in: cookie
215537
+ name: session
215538
+ `;
215539
+ return `openapi: 3.0.3
215540
+ info:
215541
+ title: ${config.projectName} API
215542
+ version: 0.1.0
215543
+ servers:
215544
+ - url: http://localhost:3000
215545
+ paths:
215546
+ ${paths}components:
215547
+ securitySchemes:
215548
+ ${securitySchemes}`;
215549
+ }
215550
+ function sqlTemplate(config) {
215551
+ const tenantColumnsSameSchema = config.tenantModel === "same_db_same_schema" ? config.database === "postgresql" ? ` tenant_id UUID NOT NULL,
215552
+ ` : ` tenant_id CHAR(36) NOT NULL,
215553
+ ` : "";
215554
+ const idColumn = config.database === "postgresql" ? `id UUID PRIMARY KEY` : `id CHAR(36) PRIMARY KEY`;
215555
+ const createdAtColumn = config.database === "postgresql" ? `created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` : `created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP`;
215556
+ if (config.database === "postgresql") {
215557
+ return `-- ArchPilot initialization SQL (PostgreSQL)
215558
+
215559
+ CREATE TABLE IF NOT EXISTS app_user (
215560
+ ${idColumn},
215561
+ ${tenantColumnsSameSchema} email TEXT NOT NULL UNIQUE,
215562
+ ${createdAtColumn}
215563
+ );
215564
+ `;
215565
+ }
215566
+ return `-- ArchPilot initialization SQL (MySQL)
215567
+
215568
+ CREATE TABLE IF NOT EXISTS app_user (
215569
+ ${idColumn},
215570
+ ${tenantColumnsSameSchema} email VARCHAR(320) NOT NULL UNIQUE,
215571
+ ${createdAtColumn}
215572
+ );
215573
+ `;
215574
+ }
215575
+ var defaultLayerRulesTemplate = `{
215576
+ "layers": ["api", "service", "domain", "infrastructure"],
215577
+ "moduleLayers": {}
215578
+ }
215579
+ `;
215580
+ var defaultDependencyRulesTemplate = `{
215581
+ "modules": {}
215582
+ }
215583
+ `;
215423
215584
  async function loadArchitectureContract(workspaceRoot) {
215424
215585
  const configPath = path8.join(workspaceRoot, ".archpilot", "architecture.json");
215425
215586
  const contents = await import_node_fs7.promises.readFile(configPath, { encoding: "utf8" }).catch(() => void 0);
@@ -215469,12 +215630,115 @@ async function saveArchitectureContract(workspaceRoot, contract) {
215469
215630
  encoding: "utf8"
215470
215631
  });
215471
215632
  }
215633
+ async function writeArchitectureConfig(workspaceRoot, inputs) {
215634
+ const baseConfig = {
215635
+ version: 1,
215636
+ ...inputs,
215637
+ rbac: false,
215638
+ createdAtUtc: nowUtcIso()
215639
+ };
215640
+ const configDir = path8.join(workspaceRoot, ".archpilot");
215641
+ const configPath = path8.join(configDir, "architecture.json");
215642
+ await ensureDir(configDir);
215643
+ let config = buildArchitectureContract(baseConfig);
215644
+ const existingContract = await loadArchitectureContract(workspaceRoot);
215645
+ if (existingContract) {
215646
+ config = buildArchitectureContract(baseConfig, existingContract);
215647
+ }
215648
+ const normalizedModulesRoot = typeof inputs.modulesRoot === "string" ? inputs.modulesRoot.trim() : "";
215649
+ if (normalizedModulesRoot.length > 0) {
215650
+ config.structure.modulesRoot = normalizedModulesRoot;
215651
+ }
215652
+ const normalizedModules = Array.isArray(inputs.modules) ? inputs.modules.map((entry) => typeof entry === "string" ? entry.trim() : "").filter((entry) => entry.length > 0).filter((entry, index, array) => array.indexOf(entry) === index) : [];
215653
+ if (normalizedModules.length > 0) {
215654
+ const moduleRegistry = {};
215655
+ for (const moduleName of normalizedModules) {
215656
+ moduleRegistry[moduleName] = {
215657
+ path: `${resolveModulesRootFromContract(config)}/${moduleName}`,
215658
+ contract: buildDefaultModuleContractPath(moduleName, config),
215659
+ publicEntrypoints: buildDefaultModulePublicEntrypoints(moduleName, config)
215660
+ };
215661
+ }
215662
+ config.modules = moduleRegistry;
215663
+ }
215664
+ const components = normalizeArchitectureComponents(inputs.components);
215665
+ if (components) {
215666
+ config.components = components;
215667
+ }
215668
+ const resources = normalizeArchitectureResources(inputs.resources);
215669
+ if (resources) {
215670
+ config.resources = resources;
215671
+ }
215672
+ await saveArchitectureContract(workspaceRoot, config);
215673
+ return config;
215674
+ }
215675
+ async function generateDocs(workspaceRoot, config) {
215676
+ await writeFileIfMissing(
215677
+ path8.join(workspaceRoot, "docs", "architecture", "overview.md"),
215678
+ overviewTemplate(config)
215679
+ );
215680
+ await writeFileIfMissing(
215681
+ path8.join(workspaceRoot, "docs", "adrs", ADR_TEMPLATE_FILE_NAME),
215682
+ canonicalAdrTemplate
215683
+ );
215684
+ await writeFileIfMissing(
215685
+ path8.join(workspaceRoot, "docs", "adrs", "adr-001-tenant-model.md"),
215686
+ adrTenantModelTemplate(config)
215687
+ );
215688
+ }
215689
+ async function generateContracts(workspaceRoot, config) {
215690
+ if (!hasConfiguredApi(config)) {
215691
+ return;
215692
+ }
215693
+ await writeFileIfMissing(
215694
+ path8.join(workspaceRoot, "contracts", "openapi.yaml"),
215695
+ openApiTemplate(config)
215696
+ );
215697
+ }
215698
+ async function generateDatabaseFiles(workspaceRoot, config) {
215699
+ if (!hasConfiguredDatabase(config)) {
215700
+ return;
215701
+ }
215702
+ await writeFileIfMissing(
215703
+ path8.join(workspaceRoot, "db", "sql", "001_init.sql"),
215704
+ sqlTemplate(config)
215705
+ );
215706
+ }
215707
+ async function generateArchpilotConfigFiles(workspaceRoot) {
215708
+ await writeFileIfMissing(
215709
+ path8.join(workspaceRoot, ".archpilot", "layer-rules.json"),
215710
+ defaultLayerRulesTemplate
215711
+ );
215712
+ await writeFileIfMissing(
215713
+ path8.join(workspaceRoot, ".archpilot", "dependency-rules.json"),
215714
+ defaultDependencyRulesTemplate
215715
+ );
215716
+ await writeFileIfMissing(
215717
+ path8.join(workspaceRoot, ".archpilot", "suppressions.json"),
215718
+ '{\n "suppressions": []\n}\n'
215719
+ );
215720
+ }
215472
215721
  async function ensureArchpilotIgnoreFile(workspaceRoot) {
215473
215722
  return writeFileIfMissing(
215474
215723
  path8.join(workspaceRoot, archpilotIgnoreFileName),
215475
215724
  defaultArchpilotIgnoreContent
215476
215725
  );
215477
215726
  }
215727
+ async function applyArchitectureInitialization(workspaceRoot, inputs) {
215728
+ const config = await writeArchitectureConfig(workspaceRoot, inputs);
215729
+ await generateDocs(workspaceRoot, config);
215730
+ await generateContracts(workspaceRoot, config);
215731
+ await generateDatabaseFiles(workspaceRoot, config);
215732
+ await generateArchpilotConfigFiles(workspaceRoot);
215733
+ const archpilotIgnoreFileCreated = await ensureArchpilotIgnoreFile(workspaceRoot);
215734
+ const policyInitResult = await ensureRiskPolicyFile(workspaceRoot);
215735
+ await refreshSetupNextStepsForWorkspace({ workspaceRoot });
215736
+ return {
215737
+ config,
215738
+ policyFileCreated: policyInitResult.created,
215739
+ archpilotIgnoreFileCreated
215740
+ };
215741
+ }
215478
215742
 
215479
215743
  // ../core/src/architectureDriftDetection.ts
215480
215744
  var driftAreaOrder = [
@@ -238052,7 +238316,7 @@ function renderGovernanceUploadSkippedByPlan() {
238052
238316
  ].join("\n");
238053
238317
  }
238054
238318
  function printUsage() {
238055
- console.error("Usage: archpilot init");
238319
+ console.error("Usage: archpilot init [--yes]");
238056
238320
  console.error("Usage: archpilot validate [--ci] [--json] [--changed] [--diff] [--base <branch>]");
238057
238321
  console.error(" archpilot impact <file-or-module> [--json] [--stdout] [--module] [--force]");
238058
238322
  console.error(" archpilot fix <RULE_ID>");
@@ -238127,7 +238391,10 @@ var cliHelpTopics = [
238127
238391
  {
238128
238392
  commandPath: ["init"],
238129
238393
  lines: [
238130
- "Usage: archpilot init"
238394
+ "Usage: archpilot init [--yes]",
238395
+ "",
238396
+ "Options:",
238397
+ " --yes accept detected Smart Init defaults without prompts"
238131
238398
  ]
238132
238399
  },
238133
238400
  {
@@ -238703,6 +238970,430 @@ function parseCloudConnectFlags(args) {
238703
238970
  ...readFlagValue(args, "--server") ? { server: readFlagValue(args, "--server") } : {}
238704
238971
  };
238705
238972
  }
238973
+ var initWizardSteps = [
238974
+ { id: "detection", title: "Detection Summary" },
238975
+ { id: "project", title: "Repository Profile" },
238976
+ { id: "components", title: "Components" },
238977
+ { id: "modules", title: "Modules and API" },
238978
+ { id: "resources", title: "Resources" },
238979
+ { id: "review", title: "Review" }
238980
+ ];
238981
+ function parseInitFlags(args) {
238982
+ if (args.some((argument) => argument !== "--yes" && argument !== "-y")) {
238983
+ return void 0;
238984
+ }
238985
+ return { yes: args.includes("--yes") || args.includes("-y") };
238986
+ }
238987
+ function isValidRepoTopology(value) {
238988
+ return value === "single_project" || value === "monorepo";
238989
+ }
238990
+ function isValidProjectKind(value) {
238991
+ return value === "backend" || value === "frontend" || value === "library";
238992
+ }
238993
+ function isValidArchitectureStyle(value) {
238994
+ return value === "modular_monolith" || value === "microservices";
238995
+ }
238996
+ function isValidDatabaseChoice(value) {
238997
+ return value === "none" || value === "postgresql" || value === "mysql";
238998
+ }
238999
+ function isValidApiStyle(value) {
239000
+ return value === "none" || value === "rest" || value === "graphql";
239001
+ }
239002
+ function isValidAuthStyle(value) {
239003
+ return value === "jwt" || value === "session";
239004
+ }
239005
+ function isValidTenantModel(value) {
239006
+ return value === "single_tenant" || value === "same_db_same_schema" || value === "same_db_different_schema";
239007
+ }
239008
+ function normalizeProjectKindsForInit(value) {
239009
+ const kinds = Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && isValidProjectKind(entry)) : [];
239010
+ return kinds.length > 0 ? [...new Set(kinds)].sort((left, right) => left.localeCompare(right)) : ["backend"];
239011
+ }
239012
+ function normalizeCanonicalStackId(value) {
239013
+ const validStacks = /* @__PURE__ */ new Set([
239014
+ "node_typescript",
239015
+ "express",
239016
+ "nestjs",
239017
+ "java",
239018
+ "spring",
239019
+ "fastapi",
239020
+ "django",
239021
+ "flask",
239022
+ "python",
239023
+ "php",
239024
+ "laravel",
239025
+ "go",
239026
+ "dotnet",
239027
+ "ruby_rails",
239028
+ "kotlin",
239029
+ "kotlin_spring",
239030
+ "rust",
239031
+ "react",
239032
+ "angular",
239033
+ "vue",
239034
+ "nextjs",
239035
+ "terraform",
239036
+ "ansible",
239037
+ "library_generic",
239038
+ "other"
239039
+ ]);
239040
+ return typeof value === "string" && validStacks.has(value) ? value : void 0;
239041
+ }
239042
+ function normalizeStackSlotsForInit(proposal, projectKinds) {
239043
+ const selections = proposal.stackSelections ?? {};
239044
+ const backendStack = normalizeCanonicalStackId(selections.backend);
239045
+ const frontendStack = normalizeCanonicalStackId(selections.frontend);
239046
+ const libraryStack = normalizeCanonicalStackId(selections.library);
239047
+ const fallbackStack = normalizeCanonicalStackId(
239048
+ proposal.detectedComponents?.find((component) => normalizeCanonicalStackId(component.stack))?.stack
239049
+ );
239050
+ return {
239051
+ ...projectKinds.includes("backend") ? { backendStack: backendStack ?? fallbackStack ?? "other" } : {},
239052
+ ...projectKinds.includes("frontend") ? { frontendStack: frontendStack ?? fallbackStack ?? "other" } : {},
239053
+ ...projectKinds.includes("library") ? { libraryStack: libraryStack ?? fallbackStack ?? "library_generic" } : {}
239054
+ };
239055
+ }
239056
+ function sanitizeArchitectureComponentsForInit(components) {
239057
+ if (!Array.isArray(components)) {
239058
+ return void 0;
239059
+ }
239060
+ const sanitized = components.map((component, index) => ({
239061
+ ...component,
239062
+ id: typeof component.id === "string" && component.id.trim().length > 0 ? component.id.trim() : `component-${index + 1}`,
239063
+ name: typeof component.name === "string" && component.name.trim().length > 0 ? component.name.trim() : typeof component.id === "string" ? component.id : `Component ${index + 1}`
239064
+ })).filter((component) => typeof component.id === "string" && component.id.length > 0);
239065
+ return sanitized.length > 0 ? sanitized : void 0;
239066
+ }
239067
+ function sanitizeArchitectureResourcesForInit(resources) {
239068
+ if (!Array.isArray(resources)) {
239069
+ return void 0;
239070
+ }
239071
+ const sanitized = resources.map((resource, index) => ({
239072
+ ...resource,
239073
+ id: typeof resource.id === "string" && resource.id.trim().length > 0 ? resource.id.trim() : `resource-${index + 1}`,
239074
+ name: typeof resource.name === "string" && resource.name.trim().length > 0 ? resource.name.trim() : typeof resource.id === "string" ? resource.id : `Resource ${index + 1}`
239075
+ })).filter((resource) => typeof resource.id === "string" && resource.id.length > 0);
239076
+ return sanitized.length > 0 ? sanitized : void 0;
239077
+ }
239078
+ function inferDatabaseFromResources(resources) {
239079
+ const databaseResource = resources?.find((resource) => resource.type === "database");
239080
+ const provider = databaseResource?.provider;
239081
+ return provider === "postgresql" || provider === "mysql" ? provider : void 0;
239082
+ }
239083
+ function buildArchitectureInputsFromSmartInit(workspaceRoot, proposal) {
239084
+ const projectKinds = normalizeProjectKindsForInit(proposal.projectKinds);
239085
+ const resources = sanitizeArchitectureResourcesForInit(proposal.resources);
239086
+ const database = typeof proposal.database === "string" && isValidDatabaseChoice(proposal.database) ? proposal.database : inferDatabaseFromResources(resources) ?? "none";
239087
+ const apiStyle = typeof proposal.apiStyle === "string" && isValidApiStyle(proposal.apiStyle) ? proposal.apiStyle : projectKinds.includes("backend") ? "rest" : "none";
239088
+ const authStyle = typeof proposal.authStyle === "string" && isValidAuthStyle(proposal.authStyle) ? proposal.authStyle : "jwt";
239089
+ return {
239090
+ projectName: path62.basename(workspaceRoot),
239091
+ repoTopology: "single_project",
239092
+ projectKinds,
239093
+ stacks: normalizeStackSlotsForInit(proposal, projectKinds),
239094
+ architectureStyle: typeof proposal.architectureStyle === "string" && isValidArchitectureStyle(proposal.architectureStyle) ? proposal.architectureStyle : projectKinds.includes("backend") && projectKinds.includes("frontend") ? "microservices" : "modular_monolith",
239095
+ database,
239096
+ tenantModel: database === "none" ? "single_tenant" : "single_tenant",
239097
+ apiStyle,
239098
+ authStyle,
239099
+ implementationProfile: typeof proposal.implementationProfile === "string" && proposal.implementationProfile.trim().length > 0 ? proposal.implementationProfile.trim() : "typescript-backend",
239100
+ ...typeof proposal.modulesRoot === "string" && proposal.modulesRoot.trim().length > 0 ? { modulesRoot: proposal.modulesRoot.trim() } : {},
239101
+ ...Array.isArray(proposal.modules) && proposal.modules.length > 0 ? {
239102
+ modules: proposal.modules.map((entry) => entry.trim()).filter((entry, index, array) => entry.length > 0 && array.indexOf(entry) === index)
239103
+ } : {},
239104
+ ...sanitizeArchitectureComponentsForInit(proposal.components) ? { components: sanitizeArchitectureComponentsForInit(proposal.components) } : {},
239105
+ ...resources ? { resources } : {}
239106
+ };
239107
+ }
239108
+ function renderSmartInitDetectionSummary(detection, inputs) {
239109
+ const proposal = detection.proposal ?? buildArchitectureProposal(detection);
239110
+ const lines = [];
239111
+ lines.push("ArchPilot Init - Smart Init Detection Summary");
239112
+ lines.push("");
239113
+ lines.push(`Repository topology: ${detection.topology.topology}`);
239114
+ lines.push(`Project kinds: ${(proposal.projectKinds ?? detection.projectKinds?.kinds ?? []).join(", ") || "(none)"}`);
239115
+ lines.push(`Detected stacks: ${detection.stacks.selectedAdapterId ?? "(none)"}`);
239116
+ lines.push(`Module roots: ${proposal.modulesRoot ?? inputs.modulesRoot ?? "(default)"}`);
239117
+ lines.push(`API style: ${proposal.apiStyle ?? inputs.apiStyle}`);
239118
+ lines.push(`Database: ${proposal.database ?? inputs.database}`);
239119
+ lines.push(`Components: ${Array.isArray(proposal.components) && proposal.components.length > 0 ? proposal.components.map((component) => `${component.id ?? "(unnamed)"}:${component.path ?? "(root)"}`).join(", ") : "(none)"}`);
239120
+ lines.push(`Resources: ${Array.isArray(proposal.resources) && proposal.resources.length > 0 ? proposal.resources.map((resource) => `${resource.id ?? "(unnamed)"}:${resource.type ?? "resource"}:${resource.provider ?? "unknown"}`).join(", ") : "(none)"}`);
239121
+ lines.push("");
239122
+ lines.push("Detected values are preselected. You can edit them before anything is written.");
239123
+ return lines.join("\n");
239124
+ }
239125
+ function renderArchitectureInputsSummary(inputs) {
239126
+ return JSON.stringify(
239127
+ {
239128
+ projectName: inputs.projectName,
239129
+ repoTopology: inputs.repoTopology,
239130
+ projectKinds: inputs.projectKinds,
239131
+ stacks: inputs.stacks,
239132
+ architectureStyle: inputs.architectureStyle,
239133
+ database: inputs.database,
239134
+ tenantModel: inputs.tenantModel,
239135
+ apiStyle: inputs.apiStyle,
239136
+ authStyle: inputs.authStyle,
239137
+ implementationProfile: inputs.implementationProfile,
239138
+ modulesRoot: inputs.modulesRoot,
239139
+ modules: inputs.modules,
239140
+ components: inputs.components,
239141
+ resources: inputs.resources
239142
+ },
239143
+ null,
239144
+ 2
239145
+ );
239146
+ }
239147
+ async function askInitQuestion(rl, prompt) {
239148
+ const answer = await rl.question(prompt);
239149
+ return answer.trim();
239150
+ }
239151
+ async function askInitValue(rl, label, current, isValid) {
239152
+ while (true) {
239153
+ const answer = await askInitQuestion(rl, `${label} [${current}]: `);
239154
+ if (answer.length === 0) {
239155
+ return current;
239156
+ }
239157
+ if (answer === "back" || answer === "cancel") {
239158
+ return answer;
239159
+ }
239160
+ if (isValid(answer)) {
239161
+ return answer;
239162
+ }
239163
+ process.stdout.write(`Valid values only. Type "back" to return or "cancel" to exit.
239164
+ `);
239165
+ }
239166
+ }
239167
+ function parseCommaList(value) {
239168
+ return value.split(",").map((entry) => entry.trim()).filter((entry, index, array) => entry.length > 0 && array.indexOf(entry) === index);
239169
+ }
239170
+ async function editProjectProfile(rl, inputs) {
239171
+ const projectName = await askInitQuestion(rl, `Project name [${inputs.projectName}]: `);
239172
+ if (projectName === "back" || projectName === "cancel") {
239173
+ return projectName;
239174
+ }
239175
+ if (projectName.length > 0) {
239176
+ inputs.projectName = projectName;
239177
+ }
239178
+ const repoTopology = await askInitValue(rl, "Repo topology (single_project, monorepo)", inputs.repoTopology, isValidRepoTopology);
239179
+ if (repoTopology === "back" || repoTopology === "cancel") {
239180
+ return repoTopology;
239181
+ }
239182
+ inputs.repoTopology = repoTopology;
239183
+ const projectKinds = await askInitQuestion(rl, `Project kinds comma-list [${inputs.projectKinds.join(", ")}]: `);
239184
+ if (projectKinds === "back" || projectKinds === "cancel") {
239185
+ return projectKinds;
239186
+ }
239187
+ if (projectKinds.length > 0) {
239188
+ const normalized = parseCommaList(projectKinds).filter(isValidProjectKind);
239189
+ if (normalized.length > 0) {
239190
+ inputs.projectKinds = [...new Set(normalized)].sort((left, right) => left.localeCompare(right));
239191
+ }
239192
+ }
239193
+ const architectureStyle = await askInitValue(
239194
+ rl,
239195
+ "Architecture style (modular_monolith, microservices)",
239196
+ inputs.architectureStyle,
239197
+ isValidArchitectureStyle
239198
+ );
239199
+ if (architectureStyle === "back" || architectureStyle === "cancel") {
239200
+ return architectureStyle;
239201
+ }
239202
+ inputs.architectureStyle = architectureStyle;
239203
+ return "next";
239204
+ }
239205
+ async function editModulesAndApi(rl, inputs) {
239206
+ const modulesRoot = await askInitQuestion(rl, `Modules root [${inputs.modulesRoot ?? ""}]: `);
239207
+ if (modulesRoot === "back" || modulesRoot === "cancel") {
239208
+ return modulesRoot;
239209
+ }
239210
+ if (modulesRoot.length > 0) {
239211
+ inputs.modulesRoot = modulesRoot;
239212
+ }
239213
+ const modules = await askInitQuestion(rl, `Modules comma-list [${inputs.modules?.join(", ") ?? ""}]: `);
239214
+ if (modules === "back" || modules === "cancel") {
239215
+ return modules;
239216
+ }
239217
+ if (modules.length > 0) {
239218
+ inputs.modules = parseCommaList(modules);
239219
+ }
239220
+ const apiStyle = await askInitValue(rl, "API style (none, rest, graphql)", inputs.apiStyle, isValidApiStyle);
239221
+ if (apiStyle === "back" || apiStyle === "cancel") {
239222
+ return apiStyle;
239223
+ }
239224
+ inputs.apiStyle = apiStyle;
239225
+ const authStyle = await askInitValue(rl, "Auth style (jwt, session)", inputs.authStyle, isValidAuthStyle);
239226
+ if (authStyle === "back" || authStyle === "cancel") {
239227
+ return authStyle;
239228
+ }
239229
+ inputs.authStyle = authStyle;
239230
+ const database = await askInitValue(rl, "Legacy database field (none, postgresql, mysql)", inputs.database, isValidDatabaseChoice);
239231
+ if (database === "back" || database === "cancel") {
239232
+ return database;
239233
+ }
239234
+ inputs.database = database;
239235
+ const tenantModel = await askInitValue(
239236
+ rl,
239237
+ "Tenant model (single_tenant, same_db_same_schema, same_db_different_schema)",
239238
+ inputs.tenantModel,
239239
+ isValidTenantModel
239240
+ );
239241
+ if (tenantModel === "back" || tenantModel === "cancel") {
239242
+ return tenantModel;
239243
+ }
239244
+ inputs.tenantModel = tenantModel;
239245
+ return "next";
239246
+ }
239247
+ async function editJsonSection(rl, label, current, onValid) {
239248
+ process.stdout.write(`${label} JSON. Press Enter to keep current value.
239249
+ `);
239250
+ process.stdout.write(`${JSON.stringify(current ?? [], null, 2)}
239251
+ `);
239252
+ const answer = await askInitQuestion(rl, `${label}: `);
239253
+ if (answer.length === 0) {
239254
+ return current;
239255
+ }
239256
+ if (answer === "back" || answer === "cancel") {
239257
+ return answer;
239258
+ }
239259
+ try {
239260
+ return onValid(JSON.parse(answer));
239261
+ } catch {
239262
+ process.stdout.write("Invalid JSON. Keeping current value.\n");
239263
+ return current;
239264
+ }
239265
+ }
239266
+ async function editComponents(rl, inputs) {
239267
+ const edited = await editJsonSection(
239268
+ rl,
239269
+ "Components",
239270
+ Array.isArray(inputs.components) ? inputs.components : void 0,
239271
+ (value) => sanitizeArchitectureComponentsForInit(Array.isArray(value) ? value : void 0)
239272
+ );
239273
+ if (edited === "back" || edited === "cancel") {
239274
+ return edited;
239275
+ }
239276
+ inputs.components = edited;
239277
+ return "next";
239278
+ }
239279
+ async function editResources(rl, inputs) {
239280
+ const edited = await editJsonSection(
239281
+ rl,
239282
+ "Resources",
239283
+ Array.isArray(inputs.resources) ? inputs.resources : void 0,
239284
+ (value) => sanitizeArchitectureResourcesForInit(Array.isArray(value) ? value : void 0)
239285
+ );
239286
+ if (edited === "back" || edited === "cancel") {
239287
+ return edited;
239288
+ }
239289
+ inputs.resources = edited;
239290
+ const inferredDatabase = inferDatabaseFromResources(edited);
239291
+ if (inferredDatabase && inputs.database === "none") {
239292
+ inputs.database = inferredDatabase;
239293
+ }
239294
+ return "next";
239295
+ }
239296
+ async function createInitQuestioner() {
239297
+ if (process.env.ARCHPILOT_INIT_ALLOW_STDIN === "1" && (!process.stdin.isTTY || !process.stdout.isTTY)) {
239298
+ const chunks = [];
239299
+ process.stdin.setEncoding("utf8");
239300
+ for await (const chunk of process.stdin) {
239301
+ chunks.push(String(chunk));
239302
+ }
239303
+ const answers = chunks.join("").split(/\r?\n/u);
239304
+ return {
239305
+ async question(prompt) {
239306
+ process.stdout.write(prompt);
239307
+ return answers.shift() ?? "";
239308
+ }
239309
+ };
239310
+ }
239311
+ return (0, import_promises2.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
239312
+ }
239313
+ function hasEditableDetectedInitValues(detection, inputs) {
239314
+ const proposal = detection.proposal ?? buildArchitectureProposal(detection);
239315
+ return !!proposal.architectureStyle || !!proposal.apiStyle || !!proposal.database || !!proposal.implementationProfile || !!proposal.modulesRoot || inputs.projectKinds.length > 0 || Object.keys(inputs.stacks).length > 0 || Array.isArray(inputs.components) && inputs.components.length > 0 || Array.isArray(inputs.resources) && inputs.resources.length > 0;
239316
+ }
239317
+ function isEditableInitStep(step, detection, inputs) {
239318
+ if (step.id === "detection") {
239319
+ return hasEditableDetectedInitValues(detection, inputs);
239320
+ }
239321
+ if (step.id === "project" || step.id === "modules") {
239322
+ return true;
239323
+ }
239324
+ if (step.id === "components") {
239325
+ return Array.isArray(inputs.components) && inputs.components.length > 0;
239326
+ }
239327
+ if (step.id === "resources") {
239328
+ return Array.isArray(inputs.resources) && inputs.resources.length > 0;
239329
+ }
239330
+ return false;
239331
+ }
239332
+ function renderInitStepPrompt(options) {
239333
+ const actions = ["Continue"];
239334
+ if (options.canEdit) {
239335
+ actions.push("Edit");
239336
+ }
239337
+ if (options.canBack) {
239338
+ actions.push("Back");
239339
+ }
239340
+ actions.push("Cancel");
239341
+ return `Press Enter to continue, or type ${actions.slice(1).map((entry) => entry.toLowerCase()).join("/")}: `;
239342
+ }
239343
+ async function runInitWizard(detection, inputs) {
239344
+ const rl = await createInitQuestioner();
239345
+ try {
239346
+ let stepIndex = 0;
239347
+ while (stepIndex < initWizardSteps.length) {
239348
+ const step = initWizardSteps[stepIndex];
239349
+ process.stdout.write(`
239350
+ [${stepIndex + 1}/${initWizardSteps.length}] ${step.title}
239351
+ `);
239352
+ if (step.id === "detection") {
239353
+ process.stdout.write(`${renderSmartInitDetectionSummary(detection, inputs)}
239354
+ `);
239355
+ } else {
239356
+ process.stdout.write(`${renderArchitectureInputsSummary(inputs)}
239357
+ `);
239358
+ }
239359
+ if (step.id === "review") {
239360
+ const answer = await askInitQuestion(rl, "Confirm and write .archpilot/architecture.json? [confirm/back/cancel]: ");
239361
+ if (answer === "confirm" || answer === "y" || answer === "yes") {
239362
+ return inputs;
239363
+ }
239364
+ if (answer === "back") {
239365
+ stepIndex = Math.max(0, stepIndex - 1);
239366
+ continue;
239367
+ }
239368
+ return void 0;
239369
+ }
239370
+ const canBack = stepIndex > 0;
239371
+ const canEdit = isEditableInitStep(step, detection, inputs);
239372
+ const action = await askInitQuestion(rl, renderInitStepPrompt({ canBack, canEdit }));
239373
+ if (action === "cancel") {
239374
+ return void 0;
239375
+ }
239376
+ if (action === "back" && canBack) {
239377
+ stepIndex = Math.max(0, stepIndex - 1);
239378
+ continue;
239379
+ }
239380
+ if (action === "edit" && canEdit) {
239381
+ const result = step.id === "detection" ? await editProjectProfile(rl, inputs) : step.id === "project" ? await editProjectProfile(rl, inputs) : step.id === "components" ? await editComponents(rl, inputs) : step.id === "modules" ? await editModulesAndApi(rl, inputs) : step.id === "resources" ? await editResources(rl, inputs) : "next";
239382
+ if (result === "cancel") {
239383
+ return void 0;
239384
+ }
239385
+ if (result === "back") {
239386
+ stepIndex = Math.max(0, stepIndex - 1);
239387
+ continue;
239388
+ }
239389
+ }
239390
+ stepIndex += 1;
239391
+ }
239392
+ return inputs;
239393
+ } finally {
239394
+ rl.close?.();
239395
+ }
239396
+ }
238706
239397
  function requiresInitializedArchitectureForCommand(command, args) {
238707
239398
  if (command === "init") {
238708
239399
  return false;
@@ -238740,27 +239431,68 @@ function requiresInitializedArchitectureForCommand(command, args) {
238740
239431
  return command === "validate" || command === "impact" || command === "fix" || command === "map" || command === "baseline" || command === "report" || command === "review" || command === "ci" || command === "docs" || command === "diagrams" || command === "badges" || command === "adr" || command === "history" || command === "health" || command === "suppressions" || command === "compliance" || command === "contracts";
238741
239432
  }
238742
239433
  async function runInitCommand(args) {
238743
- if (args.length > 0) {
238744
- console.error("Usage: archpilot init");
239434
+ const flags = parseInitFlags(args);
239435
+ if (!flags) {
239436
+ console.error("Usage: archpilot init [--yes]");
239437
+ return 1;
239438
+ }
239439
+ const workspaceRoot = process.cwd();
239440
+ const configPath = path62.join(workspaceRoot, ".archpilot", "architecture.json");
239441
+ try {
239442
+ await import_node_fs44.promises.access(configPath);
239443
+ process.stderr.write(
239444
+ "ArchPilot is already initialized for this repository. Existing .archpilot/architecture.json was preserved.\n"
239445
+ );
239446
+ process.stderr.write(
239447
+ "Edit the existing file, or remove it intentionally before running `archpilot init` again.\n"
239448
+ );
239449
+ return 1;
239450
+ } catch {
239451
+ }
239452
+ if (!flags.yes && (!process.stdin.isTTY || !process.stdout.isTTY) && process.env.ARCHPILOT_INIT_ALLOW_STDIN !== "1") {
239453
+ process.stderr.write(
239454
+ "archpilot init is interactive. Run it in a terminal, or use `archpilot init --yes` to accept Smart Init defaults without prompts.\n"
239455
+ );
238745
239456
  return 1;
238746
239457
  }
238747
239458
  try {
238748
- await ensureArchpilotIgnoreFile(process.cwd());
239459
+ const detection = detectSmartInit(workspaceRoot);
239460
+ const proposal = detection.proposal ?? buildArchitectureProposal(detection);
239461
+ let inputs = buildArchitectureInputsFromSmartInit(workspaceRoot, proposal);
239462
+ if (isValidRepoTopology(detection.topology.topology)) {
239463
+ inputs.repoTopology = detection.topology.topology;
239464
+ }
239465
+ if (!flags.yes) {
239466
+ const wizardInputs = await runInitWizard(detection, inputs);
239467
+ if (!wizardInputs) {
239468
+ process.stdout.write("ArchPilot initialization cancelled. No files were written.\n");
239469
+ return 1;
239470
+ }
239471
+ inputs = wizardInputs;
239472
+ } else {
239473
+ process.stdout.write(`${renderSmartInitDetectionSummary(detection, inputs)}
239474
+
239475
+ `);
239476
+ process.stdout.write("Accepted Smart Init defaults with --yes.\n");
239477
+ }
239478
+ const result = await applyArchitectureInitialization(workspaceRoot, inputs);
239479
+ process.stdout.write("\nArchPilot initialized.\n");
239480
+ process.stdout.write("Created .archpilot/architecture.json and baseline setup artifacts.\n");
239481
+ process.stdout.write(
239482
+ result.policyFileCreated ? "Created .archpilot/policy.json.\n" : "Preserved existing .archpilot/policy.json.\n"
239483
+ );
239484
+ process.stdout.write(
239485
+ result.archpilotIgnoreFileCreated ? "Created .archpilotignore.\n\n" : "Preserved existing .archpilotignore.\n\n"
239486
+ );
239487
+ process.stdout.write(`${renderGettingStartedChecklist()}
239488
+ `);
239489
+ return 0;
238749
239490
  } catch (error) {
238750
239491
  console.error(
238751
- error instanceof Error ? error.message : "Unable to create .archpilotignore."
239492
+ error instanceof Error ? error.message : "Unable to initialize ArchPilot."
238752
239493
  );
238753
239494
  return 1;
238754
239495
  }
238755
- process.stdout.write(
238756
- "ArchPilot initialization is Inspector-first in the VS Code extension.\n"
238757
- );
238758
- process.stdout.write(
238759
- "Open `ArchPilot: Open Architecture Inspector`, complete the initialization wizard, then confirm on the Review step to create `.archpilot/architecture.json` and baseline artifacts.\n\n"
238760
- );
238761
- process.stdout.write(`${renderGettingStartedChecklist()}
238762
- `);
238763
- return 0;
238764
239496
  }
238765
239497
  var architectureBadgeMarkdownSnippet = "[![ArchPilot Score](badges/architecture-score.svg)](.archpilot/reports/architecture-review.md)";
238766
239498
  var architectureBadgeRenderedRegex = /\[\!\[(?:ArchPilot Score|Architecture Score)\]\(badges\/architecture-score\.svg\)\]\(\.archpilot\/reports\/architecture-review\.md\)/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archpilotlabs/archpilot",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "Executable architecture governance CLI",
5
5
  "homepage": "https://archpilot.org",
6
6
  "bugs": {