archondev 2.2.0 → 2.3.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.
Files changed (2) hide show
  1. package/dist/index.js +2300 -141
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -82,7 +82,7 @@ import "./chunk-QGM4M3NI.js";
82
82
 
83
83
  // src/cli/index.ts
84
84
  import { Command as Command4 } from "commander";
85
- import chalk14 from "chalk";
85
+ import chalk15 from "chalk";
86
86
  import "dotenv/config";
87
87
 
88
88
  // src/cli/promote.ts
@@ -840,34 +840,1628 @@ async function cleanupAuto(action) {
840
840
  console.log(chalk3.green("\n\u2713 Auto cleanup disabled\n"));
841
841
  }
842
842
  }
843
- async function shouldRunAutoCleanup(cwd) {
844
- const config = await loadCleanupConfig(cwd);
845
- return config?.autoEnabled ?? false;
843
+ async function shouldRunAutoCleanup(cwd) {
844
+ const config = await loadCleanupConfig(cwd);
845
+ return config?.autoEnabled ?? false;
846
+ }
847
+ async function runAutoCleanupCheck(cwd) {
848
+ const progressPath = join3(cwd, PROGRESS_FILE);
849
+ const archonPath = join3(cwd, ARCHON_DIR);
850
+ const config = await loadCleanupConfig(cwd);
851
+ const progressMaxKb = config?.progressMaxKb ?? DEFAULT_THRESHOLDS.progressMaxKb;
852
+ const archonDirMaxMb = config?.archonDirMaxMb ?? DEFAULT_THRESHOLDS.archonDirMaxMb;
853
+ let needsAttention = false;
854
+ if (existsSync3(progressPath)) {
855
+ const size = statSync(progressPath).size;
856
+ if (size > progressMaxKb * 1024) {
857
+ needsAttention = true;
858
+ }
859
+ }
860
+ if (existsSync3(archonPath)) {
861
+ const size = getDirSize(archonPath);
862
+ if (size > archonDirMaxMb * 1024 * 1024) {
863
+ needsAttention = true;
864
+ }
865
+ }
866
+ const orphaned = getOrphanedWorktrees(cwd);
867
+ if (orphaned.length > 0) {
868
+ needsAttention = true;
869
+ }
870
+ return needsAttention;
871
+ }
872
+
873
+ // src/core/interview/intent.ts
874
+ var APP_BUILDER_PATTERNS = [
875
+ /build (a|an|my|the) (app|application|website|system|platform|service|product|tool|saas)/i,
876
+ /create (a|an|my|the) (app|application|website|system|platform|product|saas)/i,
877
+ /i want to (build|create|make|develop|design)/i,
878
+ /new (app|application|project|system|platform|product)/i,
879
+ /start (a|an|my|the|new)? ?(project|app|interview|discovery)/i,
880
+ /help me (build|create|design|plan|architect)/i,
881
+ /need (a|an|to build|to create) (new )?/i,
882
+ /let's (build|create|make|start|plan)/i,
883
+ /interview/i,
884
+ /full (stack|project|application)/i,
885
+ /from scratch/i,
886
+ /\bmvp\b/i,
887
+ /\bsaas\b/i,
888
+ /production(-| )ready/i,
889
+ /scalable/i,
890
+ /architecture for/i
891
+ ];
892
+ var AD_HOC_PATTERNS = [
893
+ /^(fix|refactor|update|modify|change|add|remove|delete|improve) /i,
894
+ /^(write|create|generate) (a |the )?(test|tests|unit test)/i,
895
+ /^(debug|bug in|issue with)/i,
896
+ /^(analyze|review|check|audit|scan) (this|these|the|my)/i,
897
+ /^(explain|how does|what is|why)/i,
898
+ /^(show me|find|search|look for|locate)/i,
899
+ /\.(ts|js|tsx|jsx|py|go|rs|java|rb|php|css|html|json|yaml|yml|md)\b/i,
900
+ /^(install|npm|yarn|pnpm|pip|cargo|go get)/i,
901
+ /^(run|execute|start|stop|restart) (the |my )?/i,
902
+ /^(lint|format|typecheck|compile|build|test) /i,
903
+ /line ?\d+/i,
904
+ /error|warning|issue/i,
905
+ /component|function|class|method|api|endpoint/i
906
+ ];
907
+ var ESCAPE_PHRASES = [
908
+ /^just start$/i,
909
+ /^skip$/i,
910
+ /^later$/i,
911
+ /^quick start$/i,
912
+ /^default(s)?$/i,
913
+ /^minimal$/i,
914
+ /^basic setup$/i
915
+ ];
916
+ function detectUserIntent(message) {
917
+ const trimmed = message.trim();
918
+ if (trimmed.length < 3) {
919
+ return {
920
+ mode: "ambiguous",
921
+ confidence: 0.3,
922
+ reasoning: "Response too short to determine intent",
923
+ suggestedAction: "clarify"
924
+ };
925
+ }
926
+ for (const pattern of ESCAPE_PHRASES) {
927
+ if (pattern.test(trimmed)) {
928
+ return {
929
+ mode: "ad_hoc",
930
+ confidence: 0.95,
931
+ reasoning: "User explicitly requested to skip interview",
932
+ suggestedAction: "execute"
933
+ };
934
+ }
935
+ }
936
+ for (const pattern of APP_BUILDER_PATTERNS) {
937
+ if (pattern.test(trimmed)) {
938
+ return {
939
+ mode: "app_builder",
940
+ confidence: 0.9,
941
+ reasoning: "User language indicates building a complete application or system",
942
+ suggestedAction: "interview"
943
+ };
944
+ }
945
+ }
946
+ for (const pattern of AD_HOC_PATTERNS) {
947
+ if (pattern.test(trimmed)) {
948
+ return {
949
+ mode: "ad_hoc",
950
+ confidence: 0.85,
951
+ reasoning: "User language indicates a specific, targeted task",
952
+ suggestedAction: "execute"
953
+ };
954
+ }
955
+ }
956
+ const wordCount = trimmed.split(/\s+/).length;
957
+ const hasFileReference = /\.(ts|js|py|go|rs|java)\b/i.test(trimmed);
958
+ const hasBuildKeywords = /(architecture|scalable|production|deploy|users|customers|clients|saas)/i.test(trimmed);
959
+ const hasTaskKeywords = /(fix|add|change|update|remove|test)/i.test(trimmed);
960
+ if (hasFileReference && wordCount < 15) {
961
+ return {
962
+ mode: "ad_hoc",
963
+ confidence: 0.7,
964
+ reasoning: "Short request with specific file reference",
965
+ suggestedAction: "execute"
966
+ };
967
+ }
968
+ if (hasBuildKeywords && wordCount > 20) {
969
+ return {
970
+ mode: "app_builder",
971
+ confidence: 0.75,
972
+ reasoning: "Detailed request with architectural/product keywords",
973
+ suggestedAction: "interview"
974
+ };
975
+ }
976
+ if (hasTaskKeywords && wordCount < 10) {
977
+ return {
978
+ mode: "ad_hoc",
979
+ confidence: 0.7,
980
+ reasoning: "Short request with task-oriented keywords",
981
+ suggestedAction: "execute"
982
+ };
983
+ }
984
+ if (wordCount > 5 && wordCount < 30) {
985
+ if (wordCount < 15) {
986
+ return {
987
+ mode: "ad_hoc",
988
+ confidence: 0.55,
989
+ reasoning: "Medium-length request without strong signals (leaning toward task)",
990
+ suggestedAction: "clarify"
991
+ };
992
+ } else {
993
+ return {
994
+ mode: "app_builder",
995
+ confidence: 0.55,
996
+ reasoning: "Longer request without strong signals (leaning toward project)",
997
+ suggestedAction: "clarify"
998
+ };
999
+ }
1000
+ }
1001
+ return {
1002
+ mode: "ambiguous",
1003
+ confidence: 0.5,
1004
+ reasoning: "Unable to determine intent from message",
1005
+ suggestedAction: "clarify"
1006
+ };
1007
+ }
1008
+ function needsClarification(result) {
1009
+ return result.confidence < 0.65 || result.suggestedAction === "clarify";
1010
+ }
1011
+ function wantsToSkip(message) {
1012
+ return ESCAPE_PHRASES.some((pattern) => pattern.test(message.trim()));
1013
+ }
1014
+ function extractProjectNameHint(message) {
1015
+ const patterns = [
1016
+ /(?:called|named)\s+["']?([a-zA-Z][a-zA-Z0-9_-]+)["']?/i,
1017
+ /["']([a-zA-Z][a-zA-Z0-9_-]+)["']\s*(?:app|project|system|tool)/i,
1018
+ /build\s+["']?([a-zA-Z][a-zA-Z0-9_-]+)["']?$/i
1019
+ ];
1020
+ for (const pattern of patterns) {
1021
+ const match = message.match(pattern);
1022
+ if (match?.[1]) {
1023
+ return match[1];
1024
+ }
1025
+ }
1026
+ return void 0;
1027
+ }
1028
+ function extractTechStackHints(message) {
1029
+ const hints = {};
1030
+ if (/typescript|\.ts\b/i.test(message)) hints.language = "typescript";
1031
+ else if (/javascript|\.js\b/i.test(message)) hints.language = "javascript";
1032
+ else if (/python|\.py\b/i.test(message)) hints.language = "python";
1033
+ else if (/go(lang)?|\.go\b/i.test(message)) hints.language = "go";
1034
+ else if (/rust|\.rs\b/i.test(message)) hints.language = "rust";
1035
+ if (/next\.?js|next/i.test(message)) hints.framework = "nextjs";
1036
+ else if (/react/i.test(message)) hints.framework = "react";
1037
+ else if (/vue/i.test(message)) hints.framework = "vue";
1038
+ else if (/svelte/i.test(message)) hints.framework = "svelte";
1039
+ else if (/express/i.test(message)) hints.framework = "express";
1040
+ else if (/fastapi/i.test(message)) hints.framework = "fastapi";
1041
+ else if (/django/i.test(message)) hints.framework = "django";
1042
+ if (/postgres(ql)?/i.test(message)) hints.database = "postgresql";
1043
+ else if (/mysql/i.test(message)) hints.database = "mysql";
1044
+ else if (/mongo(db)?/i.test(message)) hints.database = "mongodb";
1045
+ else if (/supabase/i.test(message)) hints.database = "supabase";
1046
+ else if (/firebase/i.test(message)) hints.database = "firebase";
1047
+ else if (/sqlite/i.test(message)) hints.database = "sqlite";
1048
+ if (/cli|command.line/i.test(message)) hints.type = "backend";
1049
+ else if (/api|backend|server|service/i.test(message)) hints.type = "backend";
1050
+ else if (/frontend|ui|web\s*app|spa/i.test(message)) hints.type = "frontend";
1051
+ else if (/full.?stack|website/i.test(message)) hints.type = "fullstack";
1052
+ else if (/library|package|npm|module/i.test(message)) hints.type = "library";
1053
+ return hints;
1054
+ }
1055
+
1056
+ // src/core/interview/state.ts
1057
+ function createInterviewState() {
1058
+ return {
1059
+ startedAt: /* @__PURE__ */ new Date(),
1060
+ completedPhases: [],
1061
+ skippedPhases: [],
1062
+ userWantsToSkip: false
1063
+ };
1064
+ }
1065
+ function createInterviewStateFromMessage(initialMessage, hints) {
1066
+ const state = createInterviewState();
1067
+ if (hints.projectName) state.projectName = hints.projectName;
1068
+ if (hints.language) state.language = hints.language;
1069
+ if (hints.framework) state.framework = hints.framework;
1070
+ if (hints.database) state.database = hints.database;
1071
+ if (hints.type) state.projectType = hints.type;
1072
+ if (initialMessage.length > 20) {
1073
+ state.description = initialMessage.trim();
1074
+ }
1075
+ return state;
1076
+ }
1077
+ function getKnownInfo(state) {
1078
+ const known = [];
1079
+ if (state.projectName) known.push(`Project: ${state.projectName}`);
1080
+ if (state.description) known.push(`Description: ${truncate(state.description, 50)}`);
1081
+ if (state.language) known.push(`Language: ${state.language}`);
1082
+ if (state.framework) known.push(`Framework: ${state.framework}`);
1083
+ if (state.projectType) known.push(`Type: ${state.projectType}`);
1084
+ if (state.database) known.push(`Database: ${state.database}`);
1085
+ if (state.posture) known.push(`Posture: ${state.posture}`);
1086
+ if (state.audience) known.push(`Audience: ${state.audience}`);
1087
+ return known;
1088
+ }
1089
+ function truncate(str, maxLen) {
1090
+ if (str.length <= maxLen) return str;
1091
+ return str.slice(0, maxLen - 3) + "...";
1092
+ }
1093
+ function inferPosture(state) {
1094
+ if (state.dataSensitivity === "pii" || state.dataSensitivity === "phi" || state.dataSensitivity === "pci") {
1095
+ return "enterprise";
1096
+ }
1097
+ if (state.complianceNeeds && state.complianceNeeds.length > 0) {
1098
+ return "enterprise";
1099
+ }
1100
+ if (state.audience === "personal" && !state.dataSensitivity) {
1101
+ return "prototype";
1102
+ }
1103
+ if (state.audience === "endusers") {
1104
+ return "production";
1105
+ }
1106
+ return void 0;
1107
+ }
1108
+
1109
+ // src/core/interview/phases.ts
1110
+ var PHASE_1_DISCOVERY = {
1111
+ phase: 1,
1112
+ name: "Discovery",
1113
+ description: "Understanding what you want to build and who it's for",
1114
+ questions: [
1115
+ {
1116
+ id: "elevator_pitch",
1117
+ prompt: "In one sentence, what are you building? (elevator pitch)",
1118
+ followUp: "Great! Can you tell me more about the specific problem you're solving?",
1119
+ required: true,
1120
+ validator: (answer) => ({
1121
+ valid: answer.length >= 10,
1122
+ error: "Please provide at least a brief description (10+ characters)"
1123
+ }),
1124
+ extractor: (answer, constitution) => ({
1125
+ discovery: {
1126
+ ...constitution.discovery,
1127
+ elevatorPitch: answer.trim()
1128
+ }
1129
+ })
1130
+ },
1131
+ {
1132
+ id: "target_user",
1133
+ prompt: "Who is your target user? Describe them specifically.",
1134
+ followUp: "What do they currently do to solve this problem?",
1135
+ required: true,
1136
+ extractor: (answer, constitution) => ({
1137
+ discovery: {
1138
+ ...constitution.discovery,
1139
+ targetUser: {
1140
+ ...constitution.discovery.targetUser,
1141
+ persona: answer.trim()
1142
+ }
1143
+ }
1144
+ })
1145
+ },
1146
+ {
1147
+ id: "pain_points",
1148
+ prompt: "What are their biggest pain points? (list 2-3)",
1149
+ required: true,
1150
+ extractor: (answer, constitution) => {
1151
+ const painPoints = answer.split(/[,\n]/).map((p) => p.trim()).filter((p) => p.length > 0);
1152
+ return {
1153
+ discovery: {
1154
+ ...constitution.discovery,
1155
+ targetUser: {
1156
+ ...constitution.discovery.targetUser,
1157
+ painPoints
1158
+ }
1159
+ }
1160
+ };
1161
+ }
1162
+ },
1163
+ {
1164
+ id: "core_problem",
1165
+ prompt: "What is the #1 problem your app solves?",
1166
+ required: true,
1167
+ extractor: (answer, constitution) => ({
1168
+ discovery: {
1169
+ ...constitution.discovery,
1170
+ coreProblem: answer.trim()
1171
+ }
1172
+ })
1173
+ },
1174
+ {
1175
+ id: "success_metrics",
1176
+ prompt: "How will you measure success in the first month? (optional)",
1177
+ required: false,
1178
+ extractor: (answer, constitution) => {
1179
+ if (!answer.trim()) return {};
1180
+ const metrics = answer.split(/[,\n]/).map((m) => m.trim()).filter((m) => m.length > 0);
1181
+ return {
1182
+ discovery: {
1183
+ ...constitution.discovery,
1184
+ successMetrics: metrics
1185
+ }
1186
+ };
1187
+ }
1188
+ }
1189
+ ],
1190
+ isComplete: (constitution) => {
1191
+ return !!(constitution.discovery.elevatorPitch && constitution.discovery.targetUser.persona && constitution.discovery.coreProblem);
1192
+ }
1193
+ };
1194
+ var PHASE_2_FEATURES = {
1195
+ phase: 2,
1196
+ name: "Features",
1197
+ description: "Defining what your MVP will do",
1198
+ questions: [
1199
+ {
1200
+ id: "core_features",
1201
+ prompt: "What are the 3-5 core features your MVP absolutely needs? (list them)",
1202
+ required: true,
1203
+ extractor: (answer, constitution) => {
1204
+ const featureNames = answer.split(/[,\n]/).map((f) => f.trim()).filter((f) => f.length > 0);
1205
+ const features = featureNames.map((name, index) => ({
1206
+ id: `f${index + 1}`,
1207
+ name,
1208
+ userStory: "",
1209
+ acceptanceCriteria: [],
1210
+ priority: "MVP",
1211
+ complexity: 3
1212
+ }));
1213
+ return {
1214
+ mvpFeatures: features
1215
+ };
1216
+ }
1217
+ },
1218
+ {
1219
+ id: "user_stories",
1220
+ prompt: 'For each feature, describe a user story: "As a [user], I want [feature] so that [benefit]"',
1221
+ followUp: "I'll help you refine these into proper acceptance criteria.",
1222
+ required: true,
1223
+ extractor: (answer, constitution) => {
1224
+ const stories = answer.split(/\n/).map((s) => s.trim()).filter((s) => s.length > 0);
1225
+ const updatedFeatures = [...constitution.mvpFeatures];
1226
+ for (let i = 0; i < Math.min(stories.length, updatedFeatures.length); i++) {
1227
+ const feature = updatedFeatures[i];
1228
+ if (feature) {
1229
+ feature.userStory = stories[i] ?? "";
1230
+ }
1231
+ }
1232
+ return { mvpFeatures: updatedFeatures };
1233
+ }
1234
+ },
1235
+ {
1236
+ id: "nice_to_have",
1237
+ prompt: "Any features you'd like post-MVP? (optional, helps me understand full vision)",
1238
+ required: false,
1239
+ extractor: (answer, constitution) => {
1240
+ if (!answer.trim()) return {};
1241
+ const featureNames = answer.split(/[,\n]/).map((f) => f.trim()).filter((f) => f.length > 0);
1242
+ const postMvpFeatures = featureNames.map((name, index) => ({
1243
+ id: `post${index + 1}`,
1244
+ name,
1245
+ userStory: "",
1246
+ acceptanceCriteria: [],
1247
+ priority: "POST_MVP",
1248
+ complexity: 3
1249
+ }));
1250
+ return {
1251
+ mvpFeatures: [...constitution.mvpFeatures, ...postMvpFeatures]
1252
+ };
1253
+ }
1254
+ }
1255
+ ],
1256
+ isComplete: (constitution) => {
1257
+ const mvpFeatures = constitution.mvpFeatures.filter((f) => f.priority === "MVP");
1258
+ return mvpFeatures.length >= 1 && mvpFeatures.some((f) => f.userStory);
1259
+ }
1260
+ };
1261
+ var PHASE_3_TECHNICAL = {
1262
+ phase: 3,
1263
+ name: "Technical",
1264
+ description: "Choosing your tech stack",
1265
+ questions: [
1266
+ {
1267
+ id: "tech_preference",
1268
+ prompt: `Do you have a tech stack preference? (e.g., "React + Node" or "I don't know, recommend something")`,
1269
+ required: true,
1270
+ extractor: (answer, constitution) => {
1271
+ const lower = answer.toLowerCase();
1272
+ const techStack = { ...constitution.techStack };
1273
+ if (lower.includes("next") || lower.includes("nextjs")) {
1274
+ techStack.framework = "Next.js";
1275
+ techStack.language = "TypeScript";
1276
+ } else if (lower.includes("react")) {
1277
+ techStack.framework = "React";
1278
+ techStack.language = "TypeScript";
1279
+ } else if (lower.includes("vue")) {
1280
+ techStack.framework = "Vue";
1281
+ techStack.language = "TypeScript";
1282
+ } else if (lower.includes("svelte")) {
1283
+ techStack.framework = "SvelteKit";
1284
+ techStack.language = "TypeScript";
1285
+ } else if (lower.includes("python") || lower.includes("django") || lower.includes("fastapi")) {
1286
+ techStack.language = "Python";
1287
+ if (lower.includes("django")) techStack.framework = "Django";
1288
+ if (lower.includes("fastapi")) techStack.framework = "FastAPI";
1289
+ } else if (lower.includes("don't know") || lower.includes("recommend") || lower.includes("no preference")) {
1290
+ techStack.framework = "Next.js";
1291
+ techStack.language = "TypeScript";
1292
+ }
1293
+ if (lower.includes("postgres")) {
1294
+ techStack.database = "PostgreSQL";
1295
+ } else if (lower.includes("supabase")) {
1296
+ techStack.database = "Supabase";
1297
+ } else if (lower.includes("mongo")) {
1298
+ techStack.database = "MongoDB";
1299
+ } else if (lower.includes("mysql")) {
1300
+ techStack.database = "MySQL";
1301
+ }
1302
+ return { techStack };
1303
+ }
1304
+ },
1305
+ {
1306
+ id: "database",
1307
+ prompt: 'What database would you like? (PostgreSQL, Supabase, MongoDB, or "recommend")',
1308
+ required: true,
1309
+ extractor: (answer, constitution) => {
1310
+ const lower = answer.toLowerCase();
1311
+ const techStack = { ...constitution.techStack };
1312
+ if (lower.includes("postgres")) {
1313
+ techStack.database = "PostgreSQL";
1314
+ } else if (lower.includes("supabase")) {
1315
+ techStack.database = "Supabase";
1316
+ } else if (lower.includes("mongo")) {
1317
+ techStack.database = "MongoDB";
1318
+ } else if (lower.includes("mysql")) {
1319
+ techStack.database = "MySQL";
1320
+ } else if (lower.includes("sqlite")) {
1321
+ techStack.database = "SQLite";
1322
+ } else if (lower.includes("recommend") || lower.includes("don't know")) {
1323
+ techStack.database = "Supabase";
1324
+ }
1325
+ return { techStack };
1326
+ }
1327
+ },
1328
+ {
1329
+ id: "hosting",
1330
+ prompt: 'Where do you want to host it? (Vercel, Fly.io, Railway, or "recommend")',
1331
+ required: true,
1332
+ extractor: (answer, constitution) => {
1333
+ const lower = answer.toLowerCase();
1334
+ const techStack = { ...constitution.techStack };
1335
+ if (lower.includes("vercel")) {
1336
+ techStack.hosting = "Vercel";
1337
+ } else if (lower.includes("fly")) {
1338
+ techStack.hosting = "Fly.io";
1339
+ } else if (lower.includes("railway")) {
1340
+ techStack.hosting = "Railway";
1341
+ } else if (lower.includes("netlify")) {
1342
+ techStack.hosting = "Netlify";
1343
+ } else if (lower.includes("render")) {
1344
+ techStack.hosting = "Render";
1345
+ } else if (lower.includes("local") || lower.includes("self")) {
1346
+ techStack.hosting = "Self-hosted";
1347
+ } else if (lower.includes("recommend") || lower.includes("don't know")) {
1348
+ if (techStack.framework === "Next.js") {
1349
+ techStack.hosting = "Vercel";
1350
+ } else {
1351
+ techStack.hosting = "Fly.io";
1352
+ }
1353
+ }
1354
+ return { techStack };
1355
+ }
1356
+ },
1357
+ {
1358
+ id: "auth",
1359
+ prompt: 'Do you need user authentication? If yes, any preference? (Firebase, Supabase Auth, Clerk, or "none")',
1360
+ required: false,
1361
+ extractor: (answer, constitution) => {
1362
+ const lower = answer.toLowerCase();
1363
+ const techStack = { ...constitution.techStack };
1364
+ if (lower.includes("none") || lower.includes("no")) {
1365
+ } else if (lower.includes("firebase")) {
1366
+ techStack.auth = "Firebase Auth";
1367
+ } else if (lower.includes("supabase")) {
1368
+ techStack.auth = "Supabase Auth";
1369
+ } else if (lower.includes("clerk")) {
1370
+ techStack.auth = "Clerk";
1371
+ } else if (lower.includes("auth0")) {
1372
+ techStack.auth = "Auth0";
1373
+ } else if (lower.includes("yes") || lower.includes("recommend")) {
1374
+ if (techStack.database === "Supabase") {
1375
+ techStack.auth = "Supabase Auth";
1376
+ } else {
1377
+ techStack.auth = "Firebase Auth";
1378
+ }
1379
+ }
1380
+ return { techStack };
1381
+ }
1382
+ }
1383
+ ],
1384
+ isComplete: (constitution) => {
1385
+ return !!(constitution.techStack.framework || constitution.techStack.language);
1386
+ }
1387
+ };
1388
+ var PHASE_4_BRANDING = {
1389
+ phase: 4,
1390
+ name: "Branding",
1391
+ description: "Naming and visual identity",
1392
+ questions: [
1393
+ {
1394
+ id: "project_name",
1395
+ prompt: "What's the name of your project?",
1396
+ required: true,
1397
+ validator: (answer) => ({
1398
+ valid: answer.length >= 2 && /^[a-zA-Z][a-zA-Z0-9\-_ ]*$/.test(answer),
1399
+ error: "Project name must start with a letter and contain only letters, numbers, hyphens, underscores, and spaces"
1400
+ }),
1401
+ extractor: (answer, constitution) => ({
1402
+ branding: {
1403
+ ...constitution.branding,
1404
+ projectName: answer.trim()
1405
+ }
1406
+ })
1407
+ },
1408
+ {
1409
+ id: "tagline",
1410
+ prompt: 'Got a tagline? (optional, e.g., "Project management for solo consultants")',
1411
+ required: false,
1412
+ extractor: (answer, constitution) => {
1413
+ if (!answer.trim()) return {};
1414
+ return {
1415
+ branding: {
1416
+ ...constitution.branding,
1417
+ tagline: answer.trim()
1418
+ }
1419
+ };
1420
+ }
1421
+ },
1422
+ {
1423
+ id: "domain",
1424
+ prompt: 'Any domain name in mind? (optional, e.g., "myapp.com")',
1425
+ required: false,
1426
+ extractor: (answer, constitution) => {
1427
+ if (!answer.trim()) return {};
1428
+ return {
1429
+ branding: {
1430
+ ...constitution.branding,
1431
+ domain: answer.trim().toLowerCase()
1432
+ }
1433
+ };
1434
+ }
1435
+ },
1436
+ {
1437
+ id: "colors",
1438
+ prompt: 'Any color preferences? (optional, e.g., "blue and white" or "modern dark theme")',
1439
+ required: false,
1440
+ extractor: (answer, constitution) => {
1441
+ if (!answer.trim()) return {};
1442
+ const lower = answer.toLowerCase();
1443
+ const branding = { ...constitution.branding };
1444
+ if (lower.includes("blue")) branding.primaryColor = "#3B82F6";
1445
+ else if (lower.includes("green")) branding.primaryColor = "#10B981";
1446
+ else if (lower.includes("purple")) branding.primaryColor = "#8B5CF6";
1447
+ else if (lower.includes("red")) branding.primaryColor = "#EF4444";
1448
+ else if (lower.includes("orange")) branding.primaryColor = "#F97316";
1449
+ else if (lower.includes("dark")) branding.primaryColor = "#1F2937";
1450
+ if (lower.includes("white")) branding.secondaryColor = "#FFFFFF";
1451
+ else if (lower.includes("gray") || lower.includes("grey")) branding.secondaryColor = "#6B7280";
1452
+ else if (lower.includes("dark")) branding.secondaryColor = "#374151";
1453
+ return { branding };
1454
+ }
1455
+ }
1456
+ ],
1457
+ isComplete: (constitution) => {
1458
+ return !!constitution.branding.projectName;
1459
+ }
1460
+ };
1461
+ var PHASE_5_REVIEW = {
1462
+ phase: 5,
1463
+ name: "Review",
1464
+ description: "Reviewing and finalizing your project specification",
1465
+ questions: [
1466
+ {
1467
+ id: "review_summary",
1468
+ prompt: "Here's what we have so far. Does everything look correct? (yes/no/edit)",
1469
+ required: true,
1470
+ extractor: () => ({})
1471
+ // No extraction, just confirmation
1472
+ },
1473
+ {
1474
+ id: "confirm_freeze",
1475
+ prompt: "Ready to freeze this Constitution and start building? (yes/no)",
1476
+ required: true,
1477
+ extractor: () => ({})
1478
+ // No extraction, just confirmation
1479
+ }
1480
+ ],
1481
+ isComplete: () => false
1482
+ // Review is never "auto-complete"
1483
+ };
1484
+ var INTERVIEW_PHASES = {
1485
+ 1: PHASE_1_DISCOVERY,
1486
+ 2: PHASE_2_FEATURES,
1487
+ 3: PHASE_3_TECHNICAL,
1488
+ 4: PHASE_4_BRANDING,
1489
+ 5: PHASE_5_REVIEW
1490
+ };
1491
+ function getNextPhase(current) {
1492
+ if (current >= 5) return null;
1493
+ return current + 1;
1494
+ }
1495
+ function getSkippableQuestions(phase, constitution) {
1496
+ const skippable = [];
1497
+ const phaseInfo = INTERVIEW_PHASES[phase];
1498
+ switch (phase) {
1499
+ case 1:
1500
+ if (constitution.discovery.elevatorPitch) skippable.push("elevator_pitch");
1501
+ if (constitution.discovery.targetUser.persona) skippable.push("target_user");
1502
+ if (constitution.discovery.targetUser.painPoints.length > 0) skippable.push("pain_points");
1503
+ if (constitution.discovery.coreProblem) skippable.push("core_problem");
1504
+ break;
1505
+ case 3:
1506
+ if (constitution.techStack.framework) skippable.push("tech_preference");
1507
+ if (constitution.techStack.database) skippable.push("database");
1508
+ if (constitution.techStack.hosting) skippable.push("hosting");
1509
+ if (constitution.techStack.auth) skippable.push("auth");
1510
+ break;
1511
+ case 4:
1512
+ if (constitution.branding.projectName) skippable.push("project_name");
1513
+ if (constitution.branding.tagline) skippable.push("tagline");
1514
+ if (constitution.branding.domain) skippable.push("domain");
1515
+ break;
1516
+ }
1517
+ return skippable;
1518
+ }
1519
+ function formatProgressBar(currentPhase) {
1520
+ const phases = ["Discovery", "Features", "Technical", "Branding", "Review"];
1521
+ return phases.map((name, index) => {
1522
+ const phaseNum = index + 1;
1523
+ if (phaseNum < currentPhase) {
1524
+ return `[\u2713] ${name}`;
1525
+ } else if (phaseNum === currentPhase) {
1526
+ return `[\u25CF] ${name}`;
1527
+ } else {
1528
+ return `[ ] ${name}`;
1529
+ }
1530
+ }).join("\n");
1531
+ }
1532
+
1533
+ // src/core/interview/constitution.ts
1534
+ import { createHash } from "crypto";
1535
+ function createDraftConstitution(projectId, tenantId) {
1536
+ return {
1537
+ version: "1.0.0",
1538
+ state: "DRAFT",
1539
+ projectId,
1540
+ tenantId,
1541
+ createdAt: /* @__PURE__ */ new Date(),
1542
+ discovery: {
1543
+ elevatorPitch: "",
1544
+ targetUser: {
1545
+ persona: "",
1546
+ painPoints: []
1547
+ },
1548
+ coreProblem: ""
1549
+ },
1550
+ mvpFeatures: [],
1551
+ techStack: {},
1552
+ branding: {
1553
+ projectName: ""
1554
+ },
1555
+ complexity: {
1556
+ tier: "SIMPLE",
1557
+ criteria: {
1558
+ routes: 0,
1559
+ tables: 0,
1560
+ integrations: 0,
1561
+ authRoles: 0,
1562
+ hasRealtime: false,
1563
+ hasFileUpload: false,
1564
+ hasPayments: false
1565
+ }
1566
+ },
1567
+ estimatedBuildHours: 0,
1568
+ costs: {
1569
+ buildCost: "TBD"
1570
+ }
1571
+ };
1572
+ }
1573
+ function calculateComplexity(constitution) {
1574
+ const features = constitution.mvpFeatures;
1575
+ let routes = 0;
1576
+ let tables = 0;
1577
+ const integrationSet = /* @__PURE__ */ new Set();
1578
+ let hasPayments = false;
1579
+ let hasRealtime = false;
1580
+ let hasFileUpload = false;
1581
+ for (const feature of features) {
1582
+ routes += feature.routes?.length ?? 0;
1583
+ tables += feature.tables?.length ?? 0;
1584
+ for (const integration of feature.integrations ?? []) {
1585
+ integrationSet.add(integration);
1586
+ if (integration.toLowerCase().includes("stripe") || integration.toLowerCase().includes("payment")) {
1587
+ hasPayments = true;
1588
+ }
1589
+ if (integration.toLowerCase().includes("socket") || integration.toLowerCase().includes("realtime") || integration.toLowerCase().includes("pusher")) {
1590
+ hasRealtime = true;
1591
+ }
1592
+ if (integration.toLowerCase().includes("upload") || integration.toLowerCase().includes("s3") || integration.toLowerCase().includes("storage")) {
1593
+ hasFileUpload = true;
1594
+ }
1595
+ }
1596
+ }
1597
+ const integrations = integrationSet.size;
1598
+ const authRoles = constitution.techStack.auth ? 2 : 1;
1599
+ let tier;
1600
+ if (routes <= 10 && tables <= 5 && integrations <= 2 && !hasPayments && !hasRealtime) {
1601
+ tier = "SIMPLE";
1602
+ } else if (routes <= 25 && tables <= 10 && integrations <= 5) {
1603
+ tier = "MODERATE";
1604
+ } else if (routes <= 50 && tables <= 20 && integrations <= 10) {
1605
+ tier = "COMPLEX";
1606
+ } else {
1607
+ tier = "ENTERPRISE";
1608
+ }
1609
+ return {
1610
+ tier,
1611
+ criteria: {
1612
+ routes,
1613
+ tables,
1614
+ integrations,
1615
+ authRoles,
1616
+ hasRealtime,
1617
+ hasFileUpload,
1618
+ hasPayments
1619
+ }
1620
+ };
1621
+ }
1622
+ function estimateBuildHours(complexity) {
1623
+ switch (complexity.tier) {
1624
+ case "SIMPLE":
1625
+ return 4 + complexity.criteria.routes * 0.2;
1626
+ case "MODERATE":
1627
+ return 12 + complexity.criteria.routes * 0.3;
1628
+ case "COMPLEX":
1629
+ return 24 + complexity.criteria.routes * 0.4;
1630
+ case "ENTERPRISE":
1631
+ return 48 + complexity.criteria.routes * 0.5;
1632
+ }
1633
+ }
1634
+ function estimateCosts(constitution, userTier) {
1635
+ const complexity = constitution.complexity;
1636
+ const breakdown = [];
1637
+ let buildCost;
1638
+ switch (userTier) {
1639
+ case "FREE":
1640
+ buildCost = "$0 (free tier)";
1641
+ break;
1642
+ case "BYOK":
1643
+ buildCost = "$0 to ArchonDev (your API costs)";
1644
+ break;
1645
+ case "CREDITS":
1646
+ const estimatedTokens = constitution.estimatedBuildHours * 1e5;
1647
+ const cost = estimatedTokens / 1e6 * 3 * 1.1;
1648
+ buildCost = `~$${cost.toFixed(2)} (pay-as-you-go)`;
1649
+ break;
1650
+ default:
1651
+ buildCost = "TBD";
1652
+ }
1653
+ if (constitution.techStack.hosting) {
1654
+ switch (constitution.techStack.hosting.toLowerCase()) {
1655
+ case "vercel":
1656
+ breakdown.push("Vercel: $0-20/mo");
1657
+ break;
1658
+ case "fly":
1659
+ case "fly.io":
1660
+ breakdown.push("Fly.io: $5-25/mo");
1661
+ break;
1662
+ case "railway":
1663
+ breakdown.push("Railway: $5-20/mo");
1664
+ break;
1665
+ }
1666
+ }
1667
+ if (constitution.techStack.database) {
1668
+ switch (constitution.techStack.database.toLowerCase()) {
1669
+ case "supabase":
1670
+ breakdown.push("Supabase: $0-25/mo");
1671
+ break;
1672
+ case "planetscale":
1673
+ breakdown.push("PlanetScale: $0-29/mo");
1674
+ break;
1675
+ case "postgresql":
1676
+ breakdown.push("PostgreSQL hosting: $5-15/mo");
1677
+ break;
1678
+ }
1679
+ }
1680
+ if (complexity.criteria.hasPayments) {
1681
+ breakdown.push("Stripe: 2.9% + $0.30 per transaction");
1682
+ }
1683
+ return {
1684
+ buildCost,
1685
+ infrastructureCost: breakdown.length > 0 ? `~$${breakdown.length * 10}-${breakdown.length * 30}/mo` : void 0,
1686
+ breakdown: breakdown.length > 0 ? breakdown : void 0
1687
+ };
1688
+ }
1689
+ function freezeConstitution(constitution) {
1690
+ if (constitution.state === "FROZEN") {
1691
+ throw new Error("Constitution is already frozen");
1692
+ }
1693
+ const complexity = calculateComplexity(constitution);
1694
+ const estimatedBuildHours = estimateBuildHours(complexity);
1695
+ const frozen = {
1696
+ ...constitution,
1697
+ state: "FROZEN",
1698
+ frozenAt: /* @__PURE__ */ new Date(),
1699
+ complexity,
1700
+ estimatedBuildHours
1701
+ };
1702
+ const hashContent = JSON.stringify({
1703
+ discovery: frozen.discovery,
1704
+ mvpFeatures: frozen.mvpFeatures,
1705
+ techStack: frozen.techStack,
1706
+ branding: frozen.branding
1707
+ });
1708
+ frozen.hash = createHash("sha256").update(hashContent).digest("hex");
1709
+ return frozen;
1710
+ }
1711
+ function validateConstitution(constitution) {
1712
+ const errors = [];
1713
+ const warnings = [];
1714
+ if (!constitution.discovery.elevatorPitch || constitution.discovery.elevatorPitch.length < 10) {
1715
+ errors.push("Elevator pitch is required (minimum 10 characters)");
1716
+ }
1717
+ if (!constitution.discovery.targetUser.persona) {
1718
+ errors.push("Target user persona is required");
1719
+ }
1720
+ if (!constitution.discovery.coreProblem) {
1721
+ errors.push("Core problem statement is required");
1722
+ }
1723
+ if (constitution.mvpFeatures.length === 0) {
1724
+ errors.push("At least one MVP feature is required");
1725
+ }
1726
+ const mvpCount = constitution.mvpFeatures.filter((f) => f.priority === "MVP").length;
1727
+ if (mvpCount === 0) {
1728
+ warnings.push("No features marked as MVP priority");
1729
+ }
1730
+ if (mvpCount > 10) {
1731
+ warnings.push("More than 10 MVP features may indicate scope creep");
1732
+ }
1733
+ for (const feature of constitution.mvpFeatures) {
1734
+ if (!feature.name) {
1735
+ errors.push(`Feature ${feature.id} is missing a name`);
1736
+ }
1737
+ if (!feature.userStory) {
1738
+ warnings.push(`Feature "${feature.name}" is missing a user story`);
1739
+ }
1740
+ if (feature.acceptanceCriteria.length === 0) {
1741
+ warnings.push(`Feature "${feature.name}" has no acceptance criteria`);
1742
+ }
1743
+ }
1744
+ if (!constitution.techStack.framework && !constitution.techStack.language) {
1745
+ errors.push("Tech stack must specify at least a framework or language");
1746
+ }
1747
+ if (!constitution.branding.projectName) {
1748
+ errors.push("Project name is required");
1749
+ }
1750
+ return {
1751
+ valid: errors.length === 0,
1752
+ errors,
1753
+ warnings
1754
+ };
1755
+ }
1756
+ function serializeConstitution(constitution) {
1757
+ return JSON.stringify({
1758
+ ...constitution,
1759
+ createdAt: constitution.createdAt.toISOString(),
1760
+ frozenAt: constitution.frozenAt?.toISOString()
1761
+ }, null, 2);
1762
+ }
1763
+ function deserializeConstitution(json) {
1764
+ const parsed = JSON.parse(json);
1765
+ return {
1766
+ ...parsed,
1767
+ createdAt: new Date(parsed.createdAt),
1768
+ frozenAt: parsed.frozenAt ? new Date(parsed.frozenAt) : void 0
1769
+ };
1770
+ }
1771
+ function summarizeConstitution(constitution) {
1772
+ const lines = [];
1773
+ lines.push(`# ${constitution.branding.projectName || "Untitled Project"}`);
1774
+ lines.push("");
1775
+ if (constitution.branding.tagline) {
1776
+ lines.push(`*${constitution.branding.tagline}*`);
1777
+ lines.push("");
1778
+ }
1779
+ lines.push("## Discovery");
1780
+ lines.push(`**Pitch:** ${constitution.discovery.elevatorPitch}`);
1781
+ lines.push(`**Target:** ${constitution.discovery.targetUser.persona}`);
1782
+ lines.push(`**Problem:** ${constitution.discovery.coreProblem}`);
1783
+ lines.push("");
1784
+ lines.push("## MVP Features");
1785
+ const mvpFeatures = constitution.mvpFeatures.filter((f) => f.priority === "MVP");
1786
+ for (const feature of mvpFeatures) {
1787
+ lines.push(`- **${feature.name}**: ${feature.userStory}`);
1788
+ }
1789
+ lines.push("");
1790
+ lines.push("## Tech Stack");
1791
+ if (constitution.techStack.framework) lines.push(`- Framework: ${constitution.techStack.framework}`);
1792
+ if (constitution.techStack.database) lines.push(`- Database: ${constitution.techStack.database}`);
1793
+ if (constitution.techStack.hosting) lines.push(`- Hosting: ${constitution.techStack.hosting}`);
1794
+ if (constitution.techStack.auth) lines.push(`- Auth: ${constitution.techStack.auth}`);
1795
+ lines.push("");
1796
+ lines.push("## Complexity");
1797
+ lines.push(`**Tier:** ${constitution.complexity.tier}`);
1798
+ lines.push(`**Estimated Build Time:** ${constitution.estimatedBuildHours} hours`);
1799
+ lines.push(`**Build Cost:** ${constitution.costs.buildCost}`);
1800
+ if (constitution.state === "FROZEN") {
1801
+ lines.push("");
1802
+ lines.push("---");
1803
+ lines.push(`*Constitution frozen at ${constitution.frozenAt?.toISOString()}*`);
1804
+ lines.push(`*Hash: ${constitution.hash?.substring(0, 16)}...*`);
1805
+ }
1806
+ return lines.join("\n");
1807
+ }
1808
+
1809
+ // src/core/interview/challenge.ts
1810
+ var THRESHOLDS = {
1811
+ // MVP feature limits by complexity tier
1812
+ maxMvpFeatures: {
1813
+ SIMPLE: 5,
1814
+ MODERATE: 8,
1815
+ COMPLEX: 12,
1816
+ ENTERPRISE: 20
1817
+ },
1818
+ // Complexity warnings
1819
+ simpleProjectMaxRoutes: 10,
1820
+ simpleProjectMaxTables: 5,
1821
+ // Build time warnings (hours)
1822
+ warningBuildHours: 16,
1823
+ criticalBuildHours: 40,
1824
+ // Feature quality
1825
+ minAcceptanceCriteriaPerFeature: 2,
1826
+ minUserStoryLength: 20
1827
+ };
1828
+ function analyzeForChallenges(constitution) {
1829
+ const challenges = [];
1830
+ const featuresToDefer = [];
1831
+ const recommendedActions = [];
1832
+ const mvpFeatures = constitution.mvpFeatures.filter((f) => f.priority === "MVP");
1833
+ const complexity = constitution.complexity;
1834
+ const maxFeatures = THRESHOLDS.maxMvpFeatures[complexity.tier];
1835
+ if (mvpFeatures.length > maxFeatures) {
1836
+ const excess = mvpFeatures.length - maxFeatures;
1837
+ const toDefer = suggestFeaturesToDefer(mvpFeatures, excess);
1838
+ featuresToDefer.push(...toDefer);
1839
+ challenges.push({
1840
+ type: "TOO_MANY_MVP_FEATURES",
1841
+ severity: excess > 3 ? "critical" : "warning",
1842
+ title: "Too Many MVP Features",
1843
+ message: `You have ${mvpFeatures.length} MVP features, but ${complexity.tier} projects typically have ${maxFeatures} or fewer. This increases build time and risk of delays.`,
1844
+ suggestion: `Consider moving ${excess} feature(s) to post-MVP: ${toDefer.map((f) => f.name).join(", ")}`,
1845
+ affectedFeatures: toDefer.map((f) => f.id)
1846
+ });
1847
+ recommendedActions.push(`Defer ${excess} features to post-MVP phase`);
1848
+ }
1849
+ if (complexity.tier === "COMPLEX" || complexity.tier === "ENTERPRISE") {
1850
+ challenges.push({
1851
+ type: "HIGH_COMPLEXITY",
1852
+ severity: complexity.tier === "ENTERPRISE" ? "critical" : "warning",
1853
+ title: "High Project Complexity",
1854
+ message: `This project is rated as ${complexity.tier}. It has ${complexity.criteria.routes} routes, ${complexity.criteria.tables} tables, and ${complexity.criteria.integrations} integrations.`,
1855
+ suggestion: "Consider breaking this into multiple phases or reducing initial scope."
1856
+ });
1857
+ recommendedActions.push("Review if all integrations are needed for MVP");
1858
+ }
1859
+ if (!constitution.techStack.auth && hasAuthRequiredFeatures(mvpFeatures)) {
1860
+ challenges.push({
1861
+ type: "MISSING_AUTH",
1862
+ severity: "critical",
1863
+ title: "Authentication May Be Required",
1864
+ message: "Some features appear to require user authentication, but no auth provider is selected.",
1865
+ suggestion: "Add authentication (Supabase Auth, Firebase Auth, or Clerk) to your tech stack."
1866
+ });
1867
+ recommendedActions.push("Add authentication to tech stack");
1868
+ }
1869
+ if (!constitution.techStack.database && hasDatabaseRequiredFeatures(mvpFeatures)) {
1870
+ challenges.push({
1871
+ type: "MISSING_DATABASE",
1872
+ severity: "critical",
1873
+ title: "Database May Be Required",
1874
+ message: "Some features appear to require data persistence, but no database is selected.",
1875
+ suggestion: "Add a database (Supabase, PostgreSQL, or MongoDB) to your tech stack."
1876
+ });
1877
+ recommendedActions.push("Add database to tech stack");
1878
+ }
1879
+ const vagueFeatures = mvpFeatures.filter((f) => isVagueFeature(f));
1880
+ if (vagueFeatures.length > 0) {
1881
+ challenges.push({
1882
+ type: "VAGUE_FEATURES",
1883
+ severity: "warning",
1884
+ title: "Some Features Need More Detail",
1885
+ message: `${vagueFeatures.length} feature(s) lack clear user stories or acceptance criteria.`,
1886
+ suggestion: `Add more detail to: ${vagueFeatures.map((f) => f.name).join(", ")}`,
1887
+ affectedFeatures: vagueFeatures.map((f) => f.id)
1888
+ });
1889
+ recommendedActions.push("Add user stories and acceptance criteria to vague features");
1890
+ }
1891
+ const scopeCreepScore = calculateScopeCreepScore(constitution);
1892
+ if (scopeCreepScore > 70) {
1893
+ challenges.push({
1894
+ type: "SCOPE_CREEP",
1895
+ severity: scopeCreepScore > 85 ? "critical" : "warning",
1896
+ title: "Scope Creep Detected",
1897
+ message: `This project shows signs of scope creep (score: ${scopeCreepScore}/100). Common causes: too many features, complex integrations, or unclear boundaries.`,
1898
+ suggestion: "Focus on the core problem. What is the ONE thing users need most?"
1899
+ });
1900
+ }
1901
+ if (constitution.estimatedBuildHours > THRESHOLDS.criticalBuildHours) {
1902
+ challenges.push({
1903
+ type: "UNREALISTIC_TIMELINE",
1904
+ severity: "critical",
1905
+ title: "Estimated Build Time is High",
1906
+ message: `This project is estimated to take ${Math.round(constitution.estimatedBuildHours)} hours to build. That's ${Math.round(constitution.estimatedBuildHours / 8)} full work days.`,
1907
+ suggestion: "Consider reducing scope or breaking into multiple releases."
1908
+ });
1909
+ recommendedActions.push("Break project into multiple releases");
1910
+ } else if (constitution.estimatedBuildHours > THRESHOLDS.warningBuildHours) {
1911
+ challenges.push({
1912
+ type: "UNREALISTIC_TIMELINE",
1913
+ severity: "warning",
1914
+ title: "Build Time Warning",
1915
+ message: `This project is estimated to take ${Math.round(constitution.estimatedBuildHours)} hours. Make sure this fits your timeline.`,
1916
+ suggestion: "Review features and consider which are truly essential."
1917
+ });
1918
+ }
1919
+ const scopeScore = calculateScopeCreepScore(constitution);
1920
+ return {
1921
+ shouldChallenge: challenges.length > 0,
1922
+ challenges,
1923
+ recommendedActions,
1924
+ scopeScore,
1925
+ featuresToDefer
1926
+ };
1927
+ }
1928
+ function suggestFeaturesToDefer(features, countToDefer) {
1929
+ const scored = features.map((f) => ({
1930
+ feature: f,
1931
+ score: calculateDeferralScore(f)
1932
+ }));
1933
+ scored.sort((a, b) => b.score - a.score);
1934
+ return scored.slice(0, countToDefer).map((s) => s.feature);
1935
+ }
1936
+ function calculateDeferralScore(feature) {
1937
+ let score = 0;
1938
+ score += feature.complexity * 10;
1939
+ if (isVagueFeature(feature)) {
1940
+ score += 30;
1941
+ }
1942
+ if (feature.dependencies && feature.dependencies.length > 0) {
1943
+ score -= feature.dependencies.length * 5;
1944
+ }
1945
+ if (feature.acceptanceCriteria.length < 2) {
1946
+ score += 20;
1947
+ }
1948
+ const niceToHaveKeywords = ["admin", "analytics", "export", "import", "bulk", "advanced", "custom", "premium"];
1949
+ const nameLower = feature.name.toLowerCase();
1950
+ const storyLower = feature.userStory.toLowerCase();
1951
+ for (const keyword of niceToHaveKeywords) {
1952
+ if (nameLower.includes(keyword) || storyLower.includes(keyword)) {
1953
+ score += 15;
1954
+ }
1955
+ }
1956
+ const coreKeywords = ["auth", "login", "signup", "register", "dashboard", "home", "main", "core"];
1957
+ for (const keyword of coreKeywords) {
1958
+ if (nameLower.includes(keyword)) {
1959
+ score -= 25;
1960
+ }
1961
+ }
1962
+ return score;
1963
+ }
1964
+ function isVagueFeature(feature) {
1965
+ if (!feature.userStory || feature.userStory.length < THRESHOLDS.minUserStoryLength) {
1966
+ return true;
1967
+ }
1968
+ if (feature.acceptanceCriteria.length < THRESHOLDS.minAcceptanceCriteriaPerFeature) {
1969
+ return true;
1970
+ }
1971
+ return false;
1972
+ }
1973
+ function hasAuthRequiredFeatures(features) {
1974
+ const authKeywords = ["login", "signup", "register", "account", "profile", "user", "auth", "session", "password"];
1975
+ return features.some((f) => {
1976
+ const text = `${f.name} ${f.userStory}`.toLowerCase();
1977
+ return authKeywords.some((keyword) => text.includes(keyword));
1978
+ });
1979
+ }
1980
+ function hasDatabaseRequiredFeatures(features) {
1981
+ const dbKeywords = ["save", "store", "create", "update", "delete", "list", "data", "record", "history", "track"];
1982
+ return features.some((f) => {
1983
+ const text = `${f.name} ${f.userStory}`.toLowerCase();
1984
+ return dbKeywords.some((keyword) => text.includes(keyword));
1985
+ });
1986
+ }
1987
+ function calculateScopeCreepScore(constitution) {
1988
+ let score = 0;
1989
+ const mvpFeatures = constitution.mvpFeatures.filter((f) => f.priority === "MVP");
1990
+ const complexity = constitution.complexity;
1991
+ const maxFeatures = THRESHOLDS.maxMvpFeatures[complexity.tier];
1992
+ const featureRatio = mvpFeatures.length / maxFeatures;
1993
+ score += Math.min(30, featureRatio * 20);
1994
+ switch (complexity.tier) {
1995
+ case "SIMPLE":
1996
+ score += 0;
1997
+ break;
1998
+ case "MODERATE":
1999
+ score += 10;
2000
+ break;
2001
+ case "COMPLEX":
2002
+ score += 20;
2003
+ break;
2004
+ case "ENTERPRISE":
2005
+ score += 25;
2006
+ break;
2007
+ }
2008
+ score += Math.min(20, complexity.criteria.integrations * 4);
2009
+ const vagueCount = mvpFeatures.filter((f) => isVagueFeature(f)).length;
2010
+ score += Math.min(15, vagueCount * 5);
2011
+ if (constitution.estimatedBuildHours > 40) {
2012
+ score += 10;
2013
+ } else if (constitution.estimatedBuildHours > 20) {
2014
+ score += 5;
2015
+ }
2016
+ return Math.min(100, Math.round(score));
2017
+ }
2018
+ function generateChallengePrompt(challenge) {
2019
+ const prompts = {
2020
+ TOO_MANY_MVP_FEATURES: [
2021
+ "I notice you've listed quite a few features for your MVP. The most successful launches often start smaller.",
2022
+ "Building all these features at once will take longer and cost more. What if we focused on the core experience first?",
2023
+ "Many founders find that their users only use 2-3 features regularly. Which features are absolutely essential for your first users?"
2024
+ ],
2025
+ HIGH_COMPLEXITY: [
2026
+ "This is an ambitious project! Have you considered a phased approach to reduce initial complexity?",
2027
+ "Complex projects often benefit from a 'walking skeleton' approach - build the simplest version that works end-to-end first.",
2028
+ "I want to make sure we set realistic expectations. This complexity level typically requires significant time and resources."
2029
+ ],
2030
+ MISSING_AUTH: [
2031
+ "Some of your features mention users or accounts, but we haven't set up authentication. Should we add that?",
2032
+ "User authentication is a foundational feature - it's much easier to add now than retrofit later."
2033
+ ],
2034
+ MISSING_DATABASE: [
2035
+ "Your features involve storing and retrieving data, but we haven't selected a database yet.",
2036
+ "Data persistence is core to your app. Let's make sure we have the right database for your needs."
2037
+ ],
2038
+ VAGUE_FEATURES: [
2039
+ "Some features could use more detail. Clear acceptance criteria help ensure we build exactly what you envision.",
2040
+ "I want to make sure I understand these features correctly. Can you add more specifics?"
2041
+ ],
2042
+ SCOPE_CREEP: [
2043
+ "I'm seeing signs of scope creep. What's the ONE problem you're solving for your users?",
2044
+ "The best v1 products do one thing really well. What's the core value proposition?",
2045
+ "If you could only ship ONE feature, which would it be? Let's make that amazing first."
2046
+ ],
2047
+ UNREALISTIC_TIMELINE: [
2048
+ "Based on the scope, this will take significant time to build. Is that timeline acceptable?",
2049
+ "I want to be upfront about the time investment. Would you consider a smaller initial release?"
2050
+ ]
2051
+ };
2052
+ const typePrompts = prompts[challenge.type];
2053
+ const randomIndex = Math.floor(Math.random() * typePrompts.length);
2054
+ return typePrompts[randomIndex] ?? typePrompts[0] ?? challenge.message;
2055
+ }
2056
+ function applyDeferrals(constitution, featureIds) {
2057
+ const updatedFeatures = constitution.mvpFeatures.map((feature) => {
2058
+ if (featureIds.includes(feature.id)) {
2059
+ return { ...feature, priority: "POST_MVP" };
2060
+ }
2061
+ return feature;
2062
+ });
2063
+ return {
2064
+ ...constitution,
2065
+ mvpFeatures: updatedFeatures
2066
+ };
2067
+ }
2068
+ function summarizeChallenges(result) {
2069
+ const lines = [];
2070
+ lines.push(`Scope Score: ${result.scopeScore}/100 ${getScopeEmoji(result.scopeScore)}`);
2071
+ lines.push("");
2072
+ if (result.challenges.length === 0) {
2073
+ lines.push("\u2713 No major concerns detected. Your project scope looks reasonable!");
2074
+ } else {
2075
+ lines.push(`Found ${result.challenges.length} item(s) to consider:`);
2076
+ lines.push("");
2077
+ for (const challenge of result.challenges) {
2078
+ const icon = challenge.severity === "critical" ? "\u{1F534}" : "\u{1F7E1}";
2079
+ lines.push(`${icon} **${challenge.title}**`);
2080
+ lines.push(` ${challenge.message}`);
2081
+ lines.push(` \u2192 ${challenge.suggestion}`);
2082
+ lines.push("");
2083
+ }
2084
+ }
2085
+ if (result.featuresToDefer.length > 0) {
2086
+ lines.push("**Suggested Deferrals:**");
2087
+ for (const feature of result.featuresToDefer) {
2088
+ lines.push(` \u2022 ${feature.name}`);
2089
+ }
2090
+ }
2091
+ return lines.join("\n");
2092
+ }
2093
+ function getScopeEmoji(score) {
2094
+ if (score < 30) return "\u2705";
2095
+ if (score < 50) return "\u{1F44D}";
2096
+ if (score < 70) return "\u26A0\uFE0F";
2097
+ if (score < 85) return "\u{1F7E0}";
2098
+ return "\u{1F534}";
2099
+ }
2100
+
2101
+ // src/core/interview/generator.ts
2102
+ var SETUP_ATOM_TEMPLATE = {
2103
+ title: "Project Setup & Configuration",
2104
+ description: "Initialize project structure, install dependencies, configure build tools",
2105
+ goals: [
2106
+ "Create project scaffolding",
2107
+ "Configure TypeScript/ESLint/Prettier",
2108
+ "Set up development environment",
2109
+ "Configure CI/CD basics"
2110
+ ],
2111
+ acceptanceCriteria: [
2112
+ "Project builds without errors",
2113
+ "Linting passes with zero errors",
2114
+ "Development server starts successfully",
2115
+ "Basic test suite runs"
2116
+ ],
2117
+ priority: 1,
2118
+ tags: ["setup", "infrastructure"],
2119
+ estimatedHours: 2
2120
+ };
2121
+ var AUTH_ATOM_TEMPLATE = {
2122
+ title: "Authentication System",
2123
+ goals: [
2124
+ "Implement user registration",
2125
+ "Implement user login/logout",
2126
+ "Set up session management",
2127
+ "Protect authenticated routes"
2128
+ ],
2129
+ acceptanceCriteria: [
2130
+ "Users can register with email/password",
2131
+ "Users can log in with valid credentials",
2132
+ "Invalid credentials show appropriate error",
2133
+ "Session persists across page refreshes",
2134
+ "Logout clears session"
2135
+ ],
2136
+ priority: 2,
2137
+ tags: ["auth", "security"],
2138
+ estimatedHours: 4
2139
+ };
2140
+ var DATABASE_ATOM_TEMPLATE = {
2141
+ title: "Database Schema & Migrations",
2142
+ goals: [
2143
+ "Design database schema",
2144
+ "Create migration files",
2145
+ "Set up database connection",
2146
+ "Implement data models"
2147
+ ],
2148
+ acceptanceCriteria: [
2149
+ "Migrations run without errors",
2150
+ "Schema matches requirements",
2151
+ "Connection pooling configured",
2152
+ "Models have proper TypeScript types"
2153
+ ],
2154
+ priority: 2,
2155
+ tags: ["database", "infrastructure"],
2156
+ estimatedHours: 3
2157
+ };
2158
+ function generateAtomsFromConstitution(constitution, options = {}) {
2159
+ const {
2160
+ includePostMvp = false,
2161
+ maxAtomsPerFeature = 3,
2162
+ addSetupAtom = true,
2163
+ addTestingAtom = true
2164
+ } = options;
2165
+ const atoms = [];
2166
+ const warnings = [];
2167
+ let atomIndex = 0;
2168
+ if (addSetupAtom) {
2169
+ atoms.push({
2170
+ ...SETUP_ATOM_TEMPLATE,
2171
+ featureId: "setup",
2172
+ featureName: "Project Setup",
2173
+ title: `${constitution.branding.projectName} - Project Setup`,
2174
+ description: `Initialize ${constitution.techStack.framework || "project"} with ${constitution.techStack.language || "TypeScript"}`
2175
+ });
2176
+ atomIndex++;
2177
+ }
2178
+ if (constitution.techStack.database) {
2179
+ atoms.push({
2180
+ ...DATABASE_ATOM_TEMPLATE,
2181
+ featureId: "database",
2182
+ featureName: "Database Setup",
2183
+ title: `${constitution.branding.projectName} - Database Setup (${constitution.techStack.database})`,
2184
+ description: `Set up ${constitution.techStack.database} database with initial schema`,
2185
+ acceptanceCriteria: DATABASE_ATOM_TEMPLATE.acceptanceCriteria ?? [],
2186
+ dependencies: addSetupAtom ? ["setup"] : void 0
2187
+ });
2188
+ atomIndex++;
2189
+ }
2190
+ if (constitution.techStack.auth) {
2191
+ atoms.push({
2192
+ ...AUTH_ATOM_TEMPLATE,
2193
+ featureId: "auth",
2194
+ featureName: "Authentication",
2195
+ title: `${constitution.branding.projectName} - Authentication (${constitution.techStack.auth})`,
2196
+ description: `Implement authentication using ${constitution.techStack.auth}`,
2197
+ acceptanceCriteria: AUTH_ATOM_TEMPLATE.acceptanceCriteria ?? [],
2198
+ dependencies: constitution.techStack.database ? ["database"] : addSetupAtom ? ["setup"] : void 0
2199
+ });
2200
+ atomIndex++;
2201
+ }
2202
+ const features = constitution.mvpFeatures.filter(
2203
+ (f) => f.priority === "MVP" || includePostMvp && f.priority === "POST_MVP"
2204
+ );
2205
+ for (const feature of features) {
2206
+ const featureAtoms = generateAtomsForFeature(feature, constitution, {
2207
+ maxAtoms: maxAtomsPerFeature,
2208
+ atomIndexStart: atomIndex,
2209
+ hasAuth: !!constitution.techStack.auth,
2210
+ hasDatabase: !!constitution.techStack.database
2211
+ });
2212
+ if (featureAtoms.length === 0) {
2213
+ warnings.push(`Feature "${feature.name}" generated no atoms - may need more detail`);
2214
+ }
2215
+ atoms.push(...featureAtoms);
2216
+ atomIndex += featureAtoms.length;
2217
+ }
2218
+ if (addTestingAtom && atoms.length > 2) {
2219
+ atoms.push({
2220
+ featureId: "testing",
2221
+ featureName: "Testing Suite",
2222
+ title: `${constitution.branding.projectName} - Integration Tests`,
2223
+ description: "Write integration tests for core functionality",
2224
+ goals: [
2225
+ "Set up test framework",
2226
+ "Write tests for critical paths",
2227
+ "Configure CI test runner"
2228
+ ],
2229
+ acceptanceCriteria: [
2230
+ "All critical paths have test coverage",
2231
+ "Tests pass in CI pipeline",
2232
+ "Coverage report generated"
2233
+ ],
2234
+ priority: 99,
2235
+ tags: ["testing", "qa"],
2236
+ estimatedHours: 4,
2237
+ dependencies: features.map((f) => f.id)
2238
+ });
2239
+ }
2240
+ const totalEstimatedHours = atoms.reduce(
2241
+ (sum, atom) => sum + (atom.estimatedHours ?? 2),
2242
+ 0
2243
+ );
2244
+ return {
2245
+ atoms,
2246
+ totalAtoms: atoms.length,
2247
+ totalEstimatedHours,
2248
+ warnings
2249
+ };
2250
+ }
2251
+ function generateAtomsForFeature(feature, constitution, options) {
2252
+ const atoms = [];
2253
+ const { maxAtoms, hasAuth, hasDatabase } = options;
2254
+ const complexity = feature.complexity ?? 3;
2255
+ const routeCount = feature.routes?.length ?? 0;
2256
+ const hasMultipleRoutes = routeCount > 3;
2257
+ if (complexity >= 4 || hasMultipleRoutes) {
2258
+ if (maxAtoms >= 2) {
2259
+ if (routeCount > 0 || feature.tables?.length) {
2260
+ atoms.push({
2261
+ featureId: feature.id,
2262
+ featureName: feature.name,
2263
+ title: `${feature.name} - Backend/API`,
2264
+ description: `Implement API endpoints and data layer for ${feature.name}`,
2265
+ goals: generateBackendGoals(feature),
2266
+ acceptanceCriteria: filterCriteria(feature.acceptanceCriteria, "backend"),
2267
+ priority: getPriority(feature, "backend"),
2268
+ tags: ["backend", "api", ...extractTags(feature)],
2269
+ estimatedHours: estimateHours(feature, "backend"),
2270
+ dependencies: getDependencies(feature, { hasAuth, hasDatabase })
2271
+ });
2272
+ }
2273
+ atoms.push({
2274
+ featureId: feature.id,
2275
+ featureName: feature.name,
2276
+ title: `${feature.name} - Frontend/UI`,
2277
+ description: `Implement user interface for ${feature.name}`,
2278
+ goals: generateFrontendGoals(feature),
2279
+ acceptanceCriteria: filterCriteria(feature.acceptanceCriteria, "frontend"),
2280
+ priority: getPriority(feature, "frontend"),
2281
+ tags: ["frontend", "ui", ...extractTags(feature)],
2282
+ estimatedHours: estimateHours(feature, "frontend"),
2283
+ dependencies: routeCount > 0 ? [`${feature.id}-backend`] : getDependencies(feature, { hasAuth, hasDatabase })
2284
+ });
2285
+ } else {
2286
+ atoms.push(createSingleAtom(feature, { hasAuth, hasDatabase }));
2287
+ }
2288
+ } else {
2289
+ atoms.push(createSingleAtom(feature, { hasAuth, hasDatabase }));
2290
+ }
2291
+ return atoms;
2292
+ }
2293
+ function createSingleAtom(feature, deps) {
2294
+ return {
2295
+ featureId: feature.id,
2296
+ featureName: feature.name,
2297
+ title: feature.name,
2298
+ description: feature.userStory || `Implement ${feature.name}`,
2299
+ goals: [
2300
+ `Implement ${feature.name} functionality`,
2301
+ ...feature.routes?.length ? ["Create API endpoints"] : [],
2302
+ ...feature.tables?.length ? ["Set up data models"] : [],
2303
+ "Write unit tests"
2304
+ ],
2305
+ acceptanceCriteria: feature.acceptanceCriteria.length > 0 ? feature.acceptanceCriteria : [`${feature.name} works as specified`],
2306
+ priority: featurePriorityToNumber(feature.priority),
2307
+ tags: extractTags(feature),
2308
+ estimatedHours: estimateHours(feature, "full"),
2309
+ dependencies: getDependencies(feature, deps)
2310
+ };
2311
+ }
2312
+ function generateBackendGoals(feature) {
2313
+ const goals = [];
2314
+ if (feature.routes?.length) {
2315
+ goals.push(`Implement ${feature.routes.length} API endpoint(s)`);
2316
+ }
2317
+ if (feature.tables?.length) {
2318
+ goals.push(`Create database schema for ${feature.tables.join(", ")}`);
2319
+ }
2320
+ goals.push("Add input validation");
2321
+ goals.push("Implement error handling");
2322
+ goals.push("Write API tests");
2323
+ return goals;
2324
+ }
2325
+ function generateFrontendGoals(feature) {
2326
+ return [
2327
+ `Create UI components for ${feature.name}`,
2328
+ "Implement form validation",
2329
+ "Add loading and error states",
2330
+ "Ensure responsive design",
2331
+ "Add accessibility attributes"
2332
+ ];
2333
+ }
2334
+ function filterCriteria(criteria, type) {
2335
+ if (criteria.length === 0) return ["Feature works as specified"];
2336
+ const backendKeywords = ["api", "endpoint", "database", "server", "validation", "auth"];
2337
+ const frontendKeywords = ["ui", "display", "show", "button", "form", "input", "page", "click"];
2338
+ const keywords = type === "backend" ? backendKeywords : frontendKeywords;
2339
+ const filtered = criteria.filter(
2340
+ (c) => keywords.some((k) => c.toLowerCase().includes(k))
2341
+ );
2342
+ if (filtered.length === 0) {
2343
+ const half = Math.ceil(criteria.length / 2);
2344
+ return type === "backend" ? criteria.slice(0, half) : criteria.slice(half);
2345
+ }
2346
+ return filtered;
2347
+ }
2348
+ function getPriority(feature, type) {
2349
+ const base = featurePriorityToNumber(feature.priority);
2350
+ return type === "backend" ? base : base + 1;
2351
+ }
2352
+ function featurePriorityToNumber(priority) {
2353
+ switch (priority) {
2354
+ case "MVP":
2355
+ return 10;
2356
+ case "POST_MVP":
2357
+ return 50;
2358
+ case "NICE_TO_HAVE":
2359
+ return 100;
2360
+ default:
2361
+ return 50;
2362
+ }
2363
+ }
2364
+ function estimateHours(feature, type) {
2365
+ const base = feature.complexity ?? 3;
2366
+ const routeBonus = (feature.routes?.length ?? 0) * 0.5;
2367
+ const tableBonus = (feature.tables?.length ?? 0) * 0.5;
2368
+ switch (type) {
2369
+ case "backend":
2370
+ return Math.round(base * 0.6 + routeBonus + tableBonus);
2371
+ case "frontend":
2372
+ return Math.round(base * 0.6 + 1);
2373
+ case "full":
2374
+ return Math.round(base + routeBonus + tableBonus);
2375
+ }
846
2376
  }
847
- async function runAutoCleanupCheck(cwd) {
848
- const progressPath = join3(cwd, PROGRESS_FILE);
849
- const archonPath = join3(cwd, ARCHON_DIR);
850
- const config = await loadCleanupConfig(cwd);
851
- const progressMaxKb = config?.progressMaxKb ?? DEFAULT_THRESHOLDS.progressMaxKb;
852
- const archonDirMaxMb = config?.archonDirMaxMb ?? DEFAULT_THRESHOLDS.archonDirMaxMb;
853
- let needsAttention = false;
854
- if (existsSync3(progressPath)) {
855
- const size = statSync(progressPath).size;
856
- if (size > progressMaxKb * 1024) {
857
- needsAttention = true;
2377
+ function extractTags(feature) {
2378
+ const tags = [];
2379
+ for (const integration of feature.integrations ?? []) {
2380
+ tags.push(integration.toLowerCase());
2381
+ }
2382
+ const nameLower = feature.name.toLowerCase();
2383
+ if (nameLower.includes("auth")) tags.push("auth");
2384
+ if (nameLower.includes("user")) tags.push("users");
2385
+ if (nameLower.includes("payment") || nameLower.includes("billing")) tags.push("payments");
2386
+ if (nameLower.includes("admin")) tags.push("admin");
2387
+ if (nameLower.includes("dashboard")) tags.push("dashboard");
2388
+ return [...new Set(tags)];
2389
+ }
2390
+ function getDependencies(feature, deps) {
2391
+ const dependencies = [];
2392
+ if (feature.dependencies?.length) {
2393
+ dependencies.push(...feature.dependencies);
2394
+ }
2395
+ if (deps.hasAuth && requiresAuth(feature)) {
2396
+ dependencies.push("auth");
2397
+ } else if (deps.hasDatabase && requiresDatabase(feature)) {
2398
+ dependencies.push("database");
2399
+ }
2400
+ return dependencies.length > 0 ? dependencies : void 0;
2401
+ }
2402
+ function requiresAuth(feature) {
2403
+ const authKeywords = ["user", "account", "profile", "login", "auth", "protected"];
2404
+ const text = `${feature.name} ${feature.userStory}`.toLowerCase();
2405
+ if (feature.routes?.some((r) => r.requiresAuth)) {
2406
+ return true;
2407
+ }
2408
+ return authKeywords.some((k) => text.includes(k));
2409
+ }
2410
+ function requiresDatabase(feature) {
2411
+ return (feature.tables?.length ?? 0) > 0 || (feature.routes?.length ?? 0) > 0;
2412
+ }
2413
+ function formatAsPrdJson(result, constitution) {
2414
+ return {
2415
+ projectName: constitution.branding.projectName,
2416
+ version: constitution.version,
2417
+ constitutionHash: constitution.hash,
2418
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2419
+ totalAtoms: result.totalAtoms,
2420
+ estimatedHours: result.totalEstimatedHours,
2421
+ userStories: result.atoms.map((atom, index) => ({
2422
+ id: `US-${String(index + 1).padStart(3, "0")}`,
2423
+ featureId: atom.featureId,
2424
+ title: atom.title,
2425
+ description: atom.description,
2426
+ goals: atom.goals,
2427
+ acceptanceCriteria: atom.acceptanceCriteria,
2428
+ priority: atom.priority,
2429
+ estimatedHours: atom.estimatedHours,
2430
+ tags: atom.tags,
2431
+ dependencies: atom.dependencies,
2432
+ passes: false
2433
+ }))
2434
+ };
2435
+ }
2436
+ function summarizeGeneration(result) {
2437
+ const lines = [];
2438
+ lines.push(`Generated ${result.totalAtoms} atom(s)`);
2439
+ lines.push(`Estimated total: ${result.totalEstimatedHours} hours`);
2440
+ lines.push("");
2441
+ const byFeature = /* @__PURE__ */ new Map();
2442
+ for (const atom of result.atoms) {
2443
+ const key = atom.featureName;
2444
+ if (!byFeature.has(key)) {
2445
+ byFeature.set(key, []);
858
2446
  }
2447
+ byFeature.get(key)?.push(atom);
859
2448
  }
860
- if (existsSync3(archonPath)) {
861
- const size = getDirSize(archonPath);
862
- if (size > archonDirMaxMb * 1024 * 1024) {
863
- needsAttention = true;
2449
+ lines.push("Atoms by feature:");
2450
+ for (const [feature, atoms] of byFeature) {
2451
+ lines.push(` ${feature}:`);
2452
+ for (const atom of atoms) {
2453
+ const hours = atom.estimatedHours ? `(~${atom.estimatedHours}h)` : "";
2454
+ lines.push(` \u2022 ${atom.title} ${hours}`);
864
2455
  }
865
2456
  }
866
- const orphaned = getOrphanedWorktrees(cwd);
867
- if (orphaned.length > 0) {
868
- needsAttention = true;
2457
+ if (result.warnings.length > 0) {
2458
+ lines.push("");
2459
+ lines.push("Warnings:");
2460
+ for (const warning of result.warnings) {
2461
+ lines.push(` \u26A0\uFE0F ${warning}`);
2462
+ }
869
2463
  }
870
- return needsAttention;
2464
+ return lines.join("\n");
871
2465
  }
872
2466
 
873
2467
  // src/cli/start.ts
@@ -877,7 +2471,8 @@ async function start(options = {}) {
877
2471
  console.log(chalk4.bold("\nArchonDev - AI-Powered Development Governance"));
878
2472
  console.log(chalk4.blue("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
879
2473
  }
880
- const token = await getAuthToken();
2474
+ const config = await loadConfig();
2475
+ const token = getAuthToken(config);
881
2476
  if (!token) {
882
2477
  console.log(chalk4.yellow("[!] Not logged in"));
883
2478
  const shouldLogin = await promptYesNo("Would you like to login now?", true);
@@ -1067,100 +2662,252 @@ async function displayGovernanceBanner(status2) {
1067
2662
  }
1068
2663
  console.log();
1069
2664
  }
1070
- async function handleNewProject(cwd, state) {
1071
- console.log(chalk4.bold("Starting a new project? Great!\n"));
1072
- console.log(chalk4.dim("I'll ask you a few quick questions to set things up right."));
1073
- console.log(chalk4.dim("Answer as much or as little as you want \u2014 you can always refine later.\n"));
2665
+ async function handleNewProject(cwd, _state) {
2666
+ console.log(chalk4.bold("\u{1F389} Starting a new project? Great!\n"));
2667
+ console.log(chalk4.dim("Answer as much or as little as you want \u2014 you can always refine later."));
2668
+ console.log(chalk4.dim('Type "just start" or "skip" to use defaults.\n'));
2669
+ const initialResponse = await prompt("What do you want to do?");
2670
+ if (!initialResponse.trim()) {
2671
+ console.log(chalk4.yellow("\nNo response provided. Showing options...\n"));
2672
+ await showNewProjectMenu(cwd);
2673
+ return;
2674
+ }
2675
+ const intent = detectUserIntent(initialResponse);
2676
+ if (wantsToSkip(initialResponse)) {
2677
+ console.log(chalk4.dim("\n> Using defaults \u2014 let's go!\n"));
2678
+ await quickStart(cwd);
2679
+ return;
2680
+ }
2681
+ if (intent.mode === "ad_hoc" && intent.confidence >= 0.7) {
2682
+ console.log(chalk4.dim("\n> Got it! Creating a task for this...\n"));
2683
+ const { plan: plan2 } = await import("./plan-TVTKS655.js");
2684
+ await plan2(initialResponse, {});
2685
+ return;
2686
+ }
2687
+ if (intent.mode === "app_builder" && intent.confidence >= 0.7) {
2688
+ console.log(chalk4.dim("\n> Sounds like a bigger project. Let me ask a few quick questions...\n"));
2689
+ await runConversationalInterview(cwd, initialResponse);
2690
+ return;
2691
+ }
2692
+ if (needsClarification(intent)) {
2693
+ console.log();
2694
+ console.log(chalk4.dim("I'm not sure what you're looking for. Are you..."));
2695
+ console.log();
2696
+ console.log(` ${chalk4.cyan("1")}) ${chalk4.bold("Building something new")} \u2014 a complete app, system, or project`);
2697
+ console.log(` ${chalk4.cyan("2")}) ${chalk4.bold("Doing a specific task")} \u2014 fix, add, refactor, analyze`);
2698
+ console.log(` ${chalk4.cyan("3")}) ${chalk4.bold("Just exploring")} \u2014 show me what ArchonDev can do`);
2699
+ console.log();
2700
+ const clarification = await prompt("Enter choice (1-3)");
2701
+ switch (clarification.trim()) {
2702
+ case "1":
2703
+ await runConversationalInterview(cwd, initialResponse);
2704
+ break;
2705
+ case "2":
2706
+ console.log(chalk4.dim("\n> Creating a task for this...\n"));
2707
+ const { plan: plan2 } = await import("./plan-TVTKS655.js");
2708
+ await plan2(initialResponse, {});
2709
+ break;
2710
+ case "3":
2711
+ default:
2712
+ await showNewProjectMenu(cwd);
2713
+ }
2714
+ return;
2715
+ }
2716
+ await runConversationalInterview(cwd, initialResponse);
2717
+ }
2718
+ async function showNewProjectMenu(cwd) {
1074
2719
  console.log(chalk4.bold("What would you like to do?\n"));
1075
2720
  console.log(` ${chalk4.cyan("1")}) ${chalk4.bold("Start interview")} \u2014 I'll ask questions to understand your project`);
1076
2721
  console.log(` ${chalk4.cyan("2")}) ${chalk4.bold("Quick start")} \u2014 Just create basic governance files`);
1077
- console.log(` ${chalk4.cyan("3")}) ${chalk4.bold("Import from template")} \u2014 Use a predefined project template`);
2722
+ console.log(` ${chalk4.cyan("3")}) ${chalk4.bold("Plan a task")} \u2014 Create an atom for a specific task`);
1078
2723
  console.log(` ${chalk4.cyan("q")}) ${chalk4.dim("Quit")}`);
1079
2724
  console.log();
1080
2725
  const choice = await prompt("Enter choice");
1081
2726
  switch (choice.toLowerCase()) {
1082
2727
  case "1":
1083
- await runNewProjectInterview(cwd);
2728
+ await runConversationalInterview(cwd, "");
1084
2729
  break;
1085
2730
  case "2":
1086
2731
  await quickStart(cwd);
1087
2732
  break;
1088
- case "3":
1089
- console.log(chalk4.yellow("\nTemplates coming soon! Using quick start for now.\n"));
1090
- await quickStart(cwd);
2733
+ case "3": {
2734
+ const description = await prompt("Describe what you want to do");
2735
+ if (description.trim()) {
2736
+ const { plan: plan2 } = await import("./plan-TVTKS655.js");
2737
+ await plan2(description, {});
2738
+ }
1091
2739
  break;
2740
+ }
1092
2741
  case "q":
1093
2742
  process.exit(0);
1094
2743
  default:
1095
2744
  console.log(chalk4.yellow("Invalid choice. Please try again."));
1096
- await handleNewProject(cwd, state);
1097
- }
1098
- }
1099
- async function runNewProjectInterview(cwd) {
1100
- console.log(chalk4.blue("\n-- Project Interview --\n"));
1101
- console.log(chalk4.bold("Phase 1: The Vision\n"));
1102
- const projectName = await prompt("What's the project name?");
1103
- const projectDescription = await prompt("In one sentence, what does this project do?");
1104
- const audience = await promptChoice("Who is it for?", [
1105
- { key: "1", label: "Just me (personal project)" },
1106
- { key: "2", label: "My team (internal tool)" },
1107
- { key: "3", label: "End users (product)" }
1108
- ]);
1109
- const experience = await promptChoice("Your experience level with this tech stack?", [
1110
- { key: "1", label: "\u{1F7E2} Expert \u2014 I know this well" },
1111
- { key: "2", label: "\u{1F7E1} Intermediate \u2014 I've done similar work" },
1112
- { key: "3", label: "\u{1F534} Learning \u2014 This is new to me" }
1113
- ]);
1114
- console.log(chalk4.bold("\nPhase 2: Tech Stack\n"));
1115
- const language = await promptChoice("Primary language/framework?", [
1116
- { key: "1", label: "TypeScript / JavaScript" },
1117
- { key: "2", label: "Python" },
1118
- { key: "3", label: "Go" },
1119
- { key: "4", label: "Rust" },
1120
- { key: "5", label: "Other" }
1121
- ]);
1122
- const projectType = await promptChoice("Project type?", [
1123
- { key: "1", label: "Frontend only (web UI)" },
1124
- { key: "2", label: "Backend only (API, CLI, service)" },
1125
- { key: "3", label: "Full-stack (both)" },
1126
- { key: "4", label: "Library/package" }
1127
- ]);
1128
- console.log(chalk4.bold("\nPhase 3: Preferences ") + chalk4.dim("(press Enter to skip)\n"));
1129
- const protectedFiles = await prompt("Any files AI should NEVER modify without asking? (comma-separated)");
1130
- const noNoPatterns = await prompt('Anything AI should NEVER do? (e.g., "no console.log")');
1131
- console.log(chalk4.blue("\n-- Generating Project Files --\n"));
2745
+ await showNewProjectMenu(cwd);
2746
+ }
2747
+ }
2748
+ async function runConversationalInterview(cwd, initialMessage) {
2749
+ const projectNameHint = extractProjectNameHint(initialMessage);
2750
+ const techHints = extractTechStackHints(initialMessage);
2751
+ const state = createInterviewStateFromMessage(initialMessage, {
2752
+ projectName: projectNameHint,
2753
+ ...techHints
2754
+ });
2755
+ const known = getKnownInfo(state);
2756
+ if (known.length > 0) {
2757
+ console.log(chalk4.dim("From what you said, I got:"));
2758
+ for (const info of known) {
2759
+ console.log(chalk4.dim(` \u2022 ${info}`));
2760
+ }
2761
+ console.log();
2762
+ }
2763
+ if (!state.description && !state.projectName) {
2764
+ console.log(chalk4.bold("Quick question:"));
2765
+ const description = await prompt("In one sentence, what does this project do?");
2766
+ if (wantsToSkip(description)) {
2767
+ await finishInterview(cwd, state);
2768
+ return;
2769
+ }
2770
+ state.description = description.trim() || initialMessage;
2771
+ }
2772
+ if (!state.posture && !state.audience) {
2773
+ console.log();
2774
+ console.log(chalk4.dim("Who is this for?"));
2775
+ console.log(` ${chalk4.cyan("1")}) Just me (personal/prototype)`);
2776
+ console.log(` ${chalk4.cyan("2")}) My team (internal tool)`);
2777
+ console.log(` ${chalk4.cyan("3")}) End users (production product)`);
2778
+ console.log(chalk4.dim(" (or press Enter to skip)"));
2779
+ const audienceChoice = await prompt("");
2780
+ if (wantsToSkip(audienceChoice)) {
2781
+ await finishInterview(cwd, state);
2782
+ return;
2783
+ }
2784
+ switch (audienceChoice.trim()) {
2785
+ case "1":
2786
+ state.audience = "personal";
2787
+ state.posture = "prototype";
2788
+ break;
2789
+ case "2":
2790
+ state.audience = "team";
2791
+ state.posture = "production";
2792
+ break;
2793
+ case "3":
2794
+ state.audience = "endusers";
2795
+ state.posture = "production";
2796
+ break;
2797
+ }
2798
+ }
2799
+ if (!state.language) {
2800
+ console.log();
2801
+ console.log(chalk4.dim("What language/framework?"));
2802
+ console.log(` ${chalk4.cyan("1")}) TypeScript / JavaScript`);
2803
+ console.log(` ${chalk4.cyan("2")}) Python`);
2804
+ console.log(` ${chalk4.cyan("3")}) Go`);
2805
+ console.log(` ${chalk4.cyan("4")}) Rust`);
2806
+ console.log(` ${chalk4.cyan("5")}) Other`);
2807
+ console.log(chalk4.dim(" (or press Enter to skip)"));
2808
+ const langChoice = await prompt("");
2809
+ if (!wantsToSkip(langChoice)) {
2810
+ switch (langChoice.trim()) {
2811
+ case "1":
2812
+ state.language = "typescript";
2813
+ break;
2814
+ case "2":
2815
+ state.language = "python";
2816
+ break;
2817
+ case "3":
2818
+ state.language = "go";
2819
+ break;
2820
+ case "4":
2821
+ state.language = "rust";
2822
+ break;
2823
+ case "5":
2824
+ state.language = "other";
2825
+ break;
2826
+ }
2827
+ }
2828
+ }
2829
+ if (!state.projectType) {
2830
+ console.log();
2831
+ console.log(chalk4.dim("Project type?"));
2832
+ console.log(` ${chalk4.cyan("1")}) Frontend (web UI)`);
2833
+ console.log(` ${chalk4.cyan("2")}) Backend (API, CLI, service)`);
2834
+ console.log(` ${chalk4.cyan("3")}) Full-stack (both)`);
2835
+ console.log(` ${chalk4.cyan("4")}) Library/package`);
2836
+ console.log(chalk4.dim(" (or press Enter to skip)"));
2837
+ const typeChoice = await prompt("");
2838
+ if (!wantsToSkip(typeChoice)) {
2839
+ switch (typeChoice.trim()) {
2840
+ case "1":
2841
+ state.projectType = "frontend";
2842
+ break;
2843
+ case "2":
2844
+ state.projectType = "backend";
2845
+ break;
2846
+ case "3":
2847
+ state.projectType = "fullstack";
2848
+ break;
2849
+ case "4":
2850
+ state.projectType = "library";
2851
+ break;
2852
+ }
2853
+ }
2854
+ }
2855
+ console.log();
2856
+ console.log(chalk4.dim("Any files AI should NEVER modify? ") + chalk4.dim("(comma-separated, or Enter to skip)"));
2857
+ const protectedFilesInput = await prompt("");
2858
+ if (protectedFilesInput.trim() && !wantsToSkip(protectedFilesInput)) {
2859
+ state.protectedFiles = protectedFilesInput.split(",").map((f) => f.trim()).filter(Boolean);
2860
+ }
2861
+ if (!state.posture) {
2862
+ state.posture = inferPosture(state) ?? "production";
2863
+ }
2864
+ await finishInterview(cwd, state);
2865
+ }
2866
+ async function finishInterview(cwd, state) {
2867
+ console.log(chalk4.blue("\n-- Setting Up Project --\n"));
2868
+ const posture = state.posture === "enterprise" ? "enterprise" : state.posture === "prototype" ? "prototype" : "production";
1132
2869
  const { init: init2 } = await import("./init-6EXMDCWC.js");
1133
- await init2({ analyze: false, git: true });
2870
+ await init2({ analyze: false, git: true, posture });
1134
2871
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1135
2872
  const progressEntry = `
1136
2873
  ## ${today} - Project Initialized via Interview
1137
2874
 
1138
2875
  ### Vision
1139
- - **Name:** ${projectName || "Unnamed Project"}
1140
- - **Description:** ${projectDescription || "No description provided"}
1141
- - **Audience:** ${["Personal", "Team", "End Users"][parseInt(audience) - 1] || "Not specified"}
1142
- - **Experience Level:** ${["Expert", "Intermediate", "Learning"][parseInt(experience) - 1] || "Not specified"}
2876
+ - **Name:** ${state.projectName || "Unnamed Project"}
2877
+ - **Description:** ${state.description || "No description provided"}
2878
+ - **Audience:** ${state.audience || "Not specified"}
2879
+ - **Posture:** ${state.posture || "production"}
1143
2880
 
1144
2881
  ### Stack
1145
- - **Language:** ${["TypeScript/JavaScript", "Python", "Go", "Rust", "Other"][parseInt(language) - 1] || "Not specified"}
1146
- - **Type:** ${["Frontend", "Backend", "Full-stack", "Library"][parseInt(projectType) - 1] || "Not specified"}
2882
+ - **Language:** ${state.language || "Not specified"}
2883
+ - **Framework:** ${state.framework || "Not specified"}
2884
+ - **Type:** ${state.projectType || "Not specified"}
2885
+ - **Database:** ${state.database || "Not specified"}
1147
2886
 
1148
2887
  ### Preferences
1149
- ${protectedFiles ? `- **Protected files:** ${protectedFiles}` : "- No protected files specified"}
1150
- ${noNoPatterns ? `- **Forbidden patterns:** ${noNoPatterns}` : "- No forbidden patterns specified"}
2888
+ ${state.protectedFiles?.length ? `- **Protected files:** ${state.protectedFiles.join(", ")}` : "- No protected files specified"}
2889
+ ${state.forbiddenPatterns?.length ? `- **Forbidden patterns:** ${state.forbiddenPatterns.join(", ")}` : "- No forbidden patterns specified"}
1151
2890
 
1152
2891
  ### Files Created
1153
- - ARCHITECTURE.md
2892
+ - ARCHITECTURE.md (posture: ${posture})
1154
2893
  - .archon/config.yaml
1155
2894
  - progress.txt
1156
2895
  `;
1157
2896
  const progressPath = join4(cwd, "progress.txt");
1158
2897
  if (!existsSync4(progressPath)) {
1159
- const { writeFileSync } = await import("fs");
1160
- writeFileSync(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
2898
+ const { writeFileSync: writeFileSync2 } = await import("fs");
2899
+ writeFileSync2(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
1161
2900
  }
1162
2901
  appendFileSync(progressPath, progressEntry);
1163
2902
  console.log(chalk4.green("\n\u2713 Project initialized!\n"));
2903
+ const finalKnown = getKnownInfo(state);
2904
+ if (finalKnown.length > 0) {
2905
+ console.log(chalk4.dim("Project configured with:"));
2906
+ for (const info of finalKnown) {
2907
+ console.log(chalk4.dim(` \u2022 ${info}`));
2908
+ }
2909
+ console.log();
2910
+ }
1164
2911
  console.log(chalk4.bold("Next steps:"));
1165
2912
  console.log(` 1. ${chalk4.cyan("Review")} ARCHITECTURE.md and customize if needed`);
1166
2913
  console.log(` 2. ${chalk4.cyan("Run")} ${chalk4.dim('archon plan "your first task"')} to create an atom`);
@@ -1181,8 +2928,8 @@ async function quickStart(cwd) {
1181
2928
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1182
2929
  const progressPath = join4(cwd, "progress.txt");
1183
2930
  if (!existsSync4(progressPath)) {
1184
- const { writeFileSync } = await import("fs");
1185
- writeFileSync(progressPath, `# ArchonDev Progress Log
2931
+ const { writeFileSync: writeFileSync2 } = await import("fs");
2932
+ writeFileSync2(progressPath, `# ArchonDev Progress Log
1186
2933
 
1187
2934
  This file tracks learnings and decisions across sessions.
1188
2935
 
@@ -1240,8 +2987,8 @@ async function analyzeAndAdapt(cwd) {
1240
2987
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1241
2988
  const progressPath = join4(cwd, "progress.txt");
1242
2989
  if (!existsSync4(progressPath)) {
1243
- const { writeFileSync } = await import("fs");
1244
- writeFileSync(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
2990
+ const { writeFileSync: writeFileSync2 } = await import("fs");
2991
+ writeFileSync2(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
1245
2992
  }
1246
2993
  appendFileSync(progressPath, `
1247
2994
  ## ${today} - ArchonDev Adapted to Existing Project
@@ -1293,8 +3040,8 @@ async function quickAdapt(cwd) {
1293
3040
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1294
3041
  const progressPath = join4(cwd, "progress.txt");
1295
3042
  if (!existsSync4(progressPath)) {
1296
- const { writeFileSync } = await import("fs");
1297
- writeFileSync(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
3043
+ const { writeFileSync: writeFileSync2 } = await import("fs");
3044
+ writeFileSync2(progressPath, "# ArchonDev Progress Log\n\nThis file tracks learnings and decisions across sessions.\n");
1298
3045
  }
1299
3046
  appendFileSync(progressPath, `
1300
3047
  ## ${today} - Quick Adapt (Defaults)
@@ -1539,22 +3286,6 @@ function promptYesNo(question, defaultValue) {
1539
3286
  });
1540
3287
  });
1541
3288
  }
1542
- function promptChoice(question, options) {
1543
- return new Promise((resolve) => {
1544
- console.log(`${chalk4.cyan("?")} ${question}`);
1545
- for (const opt of options) {
1546
- console.log(` ${chalk4.dim(opt.key)}) ${opt.label}`);
1547
- }
1548
- const rl = readline.createInterface({
1549
- input: process.stdin,
1550
- output: process.stdout
1551
- });
1552
- rl.question(` ${chalk4.dim("Enter choice")}: `, (answer) => {
1553
- rl.close();
1554
- resolve(answer.trim() || "1");
1555
- });
1556
- });
1557
- }
1558
3289
 
1559
3290
  // src/cli/credits.ts
1560
3291
  import chalk5 from "chalk";
@@ -2574,7 +4305,7 @@ async function a11yCheck(options) {
2574
4305
  console.log(chalk7.dim(`Full report saved to: .archon/a11y-report.json`));
2575
4306
  }
2576
4307
  async function a11yFix(options) {
2577
- const prompt2 = createPrompt();
4308
+ const prompt3 = createPrompt();
2578
4309
  try {
2579
4310
  console.log(chalk7.blue("\n\u267F Accessibility Auto-Fix\n"));
2580
4311
  const reportPath = join6(process.cwd(), ".archon/a11y-report.json");
@@ -2651,7 +4382,7 @@ ${totalFixes} fixes would be applied. Run without --dry-run to apply.`));
2651
4382
  console.log(chalk7.dim('Run "archon a11y check" to verify fixes.'));
2652
4383
  }
2653
4384
  } finally {
2654
- prompt2.close();
4385
+ prompt3.close();
2655
4386
  }
2656
4387
  }
2657
4388
  async function a11yBadge(options) {
@@ -2689,7 +4420,7 @@ async function a11yBadge(options) {
2689
4420
  }
2690
4421
  }
2691
4422
  async function a11yPreDeploy() {
2692
- const prompt2 = createPrompt();
4423
+ const prompt3 = createPrompt();
2693
4424
  try {
2694
4425
  console.log(chalk7.blue("\n[CHECK] Pre-Deploy Accessibility\n"));
2695
4426
  console.log(chalk7.dim("Before deploying a live website, accessibility compliance is required.\n"));
@@ -2701,7 +4432,7 @@ async function a11yPreDeploy() {
2701
4432
  const reportContent = await readFile5(reportPath, "utf-8");
2702
4433
  const report = JSON.parse(reportContent);
2703
4434
  if (report.passed) {
2704
- const addBadge = await prompt2.ask("\nWould you like to add a WCAG 2.2 AA badge to your footer? (y/N): ");
4435
+ const addBadge = await prompt3.ask("\nWould you like to add a WCAG 2.2 AA badge to your footer? (y/N): ");
2705
4436
  if (addBadge.toLowerCase() === "y") {
2706
4437
  await a11yBadge({});
2707
4438
  }
@@ -2711,7 +4442,7 @@ async function a11yPreDeploy() {
2711
4442
  console.log(" 1) Fix issues now (recommended)");
2712
4443
  console.log(" 2) Deploy anyway (not recommended)");
2713
4444
  console.log(" 3) Cancel deployment\n");
2714
- const choice = await prompt2.ask("Which would you like to do? (1/2/3): ");
4445
+ const choice = await prompt3.ask("Which would you like to do? (1/2/3): ");
2715
4446
  if (choice === "1") {
2716
4447
  await a11yFix({});
2717
4448
  await a11yCheck({});
@@ -2725,7 +4456,7 @@ async function a11yPreDeploy() {
2725
4456
  return false;
2726
4457
  }
2727
4458
  } finally {
2728
- prompt2.close();
4459
+ prompt3.close();
2729
4460
  }
2730
4461
  }
2731
4462
 
@@ -2820,7 +4551,7 @@ Guidelines:
2820
4551
  Output your response as valid JSON.`;
2821
4552
  async function generateIdentityCandidates(pageContent) {
2822
4553
  const agent = new ArchitectAgent({ temperature: 0.8 });
2823
- const prompt2 = `Based on the following website content, generate brand identity options.
4554
+ const prompt3 = `Based on the following website content, generate brand identity options.
2824
4555
 
2825
4556
  ${pageContent}
2826
4557
 
@@ -2841,7 +4572,7 @@ Output as JSON:
2841
4572
  }`;
2842
4573
  const response = await agent.client.chat(
2843
4574
  IDENTITY_SYSTEM_PROMPT,
2844
- prompt2,
4575
+ prompt3,
2845
4576
  { temperature: 0.8, maxTokens: 2e3 }
2846
4577
  );
2847
4578
  const jsonMatch = response.content.match(/\{[\s\S]*\}/);
@@ -2853,7 +4584,7 @@ Output as JSON:
2853
4584
  }
2854
4585
  async function geoIdentity() {
2855
4586
  const cwd = process.cwd();
2856
- const prompt2 = createPrompt2();
4587
+ const prompt3 = createPrompt2();
2857
4588
  try {
2858
4589
  console.log(chalk8.blue("\n\u{1F3AF} GEO Identity Generator\n"));
2859
4590
  const { allowed, tier } = await checkStrongModelAccess();
@@ -2884,7 +4615,7 @@ async function geoIdentity() {
2884
4615
  console.log(` ${chalk8.dim(p.rationale)}
2885
4616
  `);
2886
4617
  });
2887
- const phraseChoice = await prompt2.ask('Select a phrase (1-3) or "r" to regenerate: ');
4618
+ const phraseChoice = await prompt3.ask('Select a phrase (1-3) or "r" to regenerate: ');
2888
4619
  if (phraseChoice.toLowerCase() === "r") {
2889
4620
  console.log(chalk8.dim("\nRegenerating... Run the command again.\n"));
2890
4621
  return;
@@ -2901,7 +4632,7 @@ async function geoIdentity() {
2901
4632
  console.log(` ${chalk8.dim(d.rationale)}
2902
4633
  `);
2903
4634
  });
2904
- const descChoice = await prompt2.ask('Select a description (1-3) or "r" to regenerate: ');
4635
+ const descChoice = await prompt3.ask('Select a description (1-3) or "r" to regenerate: ');
2905
4636
  if (descChoice.toLowerCase() === "r") {
2906
4637
  console.log(chalk8.dim("\nRegenerating... Run the command again.\n"));
2907
4638
  return;
@@ -2926,7 +4657,7 @@ async function geoIdentity() {
2926
4657
  console.log();
2927
4658
  console.log(chalk8.cyan(`Use 'archon geo schema' to generate JSON-LD.`));
2928
4659
  } finally {
2929
- prompt2.close();
4660
+ prompt3.close();
2930
4661
  }
2931
4662
  }
2932
4663
  async function geoSchema(options) {
@@ -3043,7 +4774,7 @@ async function geoFaq(options) {
3043
4774
  }
3044
4775
  console.log(chalk8.dim("Generating FAQ content with AI...\n"));
3045
4776
  const agent = new ArchitectAgent({ temperature: 0.7 });
3046
- const prompt2 = `Generate FAQ content for a product/service with:
4777
+ const prompt3 = `Generate FAQ content for a product/service with:
3047
4778
  - Brand phrase: "${identityPhrase}"
3048
4779
  - Description: "${shortDescription}"
3049
4780
 
@@ -3056,7 +4787,7 @@ Generate 6-8 FAQs as JSON:
3056
4787
  }`;
3057
4788
  const response = await agent.client.chat(
3058
4789
  FAQ_SYSTEM_PROMPT,
3059
- prompt2,
4790
+ prompt3,
3060
4791
  { temperature: 0.7, maxTokens: 2e3 }
3061
4792
  );
3062
4793
  const jsonMatch = response.content.match(/\{[\s\S]*\}/);
@@ -3418,7 +5149,7 @@ async function seoCheck(options) {
3418
5149
  Full report saved to: .archon/seo-report.json`));
3419
5150
  }
3420
5151
  async function seoFix(options) {
3421
- const prompt2 = createPrompt3();
5152
+ const prompt3 = createPrompt3();
3422
5153
  try {
3423
5154
  console.log(chalk9.blue("\n\u{1F527} SEO Auto-Fix\n"));
3424
5155
  const reportPath = join8(process.cwd(), ".archon/seo-report.json");
@@ -3474,7 +5205,7 @@ async function seoFix(options) {
3474
5205
  console.log(chalk9.green(` + ${tag.slice(0, 70)}${tag.length > 70 ? "..." : ""}`));
3475
5206
  }
3476
5207
  if (!options.dryRun) {
3477
- const confirm = await prompt2.ask(chalk9.dim(" Apply these changes? (y/N): "));
5208
+ const confirm = await prompt3.ask(chalk9.dim(" Apply these changes? (y/N): "));
3478
5209
  if (confirm.toLowerCase() === "y") {
3479
5210
  await writeFile7(filePath, newContent);
3480
5211
  totalFixes += tagsToAdd.length;
@@ -3497,11 +5228,11 @@ ${totalFixes} fixes would be applied. Run without --dry-run to apply.`));
3497
5228
  console.log(chalk9.dim('Run "archon seo check" to verify fixes.'));
3498
5229
  }
3499
5230
  } finally {
3500
- prompt2.close();
5231
+ prompt3.close();
3501
5232
  }
3502
5233
  }
3503
5234
  async function seoOpenGraph(options) {
3504
- const prompt2 = createPrompt3();
5235
+ const prompt3 = createPrompt3();
3505
5236
  try {
3506
5237
  console.log(chalk9.blue("\n\u{1F4F1} Add Open Graph Tags\n"));
3507
5238
  let targetFile;
@@ -3520,7 +5251,7 @@ async function seoOpenGraph(options) {
3520
5251
  if (files.length > 10) {
3521
5252
  console.log(chalk9.dim(` ... and ${files.length - 10} more`));
3522
5253
  }
3523
- const fileChoice = await prompt2.ask("\nEnter file path or number: ");
5254
+ const fileChoice = await prompt3.ask("\nEnter file path or number: ");
3524
5255
  const num = parseInt(fileChoice, 10);
3525
5256
  if (num > 0 && num <= files.length) {
3526
5257
  targetFile = files[num - 1] ?? "";
@@ -3532,10 +5263,10 @@ async function seoOpenGraph(options) {
3532
5263
  console.log(chalk9.red(`File not found: ${targetFile}`));
3533
5264
  return;
3534
5265
  }
3535
- const ogTitle = await prompt2.ask("og:title (page title for social): ");
3536
- const ogDescription = await prompt2.ask("og:description (page description): ");
3537
- const ogImage = await prompt2.ask("og:image (full URL to image): ");
3538
- const ogUrl = await prompt2.ask("og:url (canonical page URL): ");
5266
+ const ogTitle = await prompt3.ask("og:title (page title for social): ");
5267
+ const ogDescription = await prompt3.ask("og:description (page description): ");
5268
+ const ogImage = await prompt3.ask("og:image (full URL to image): ");
5269
+ const ogUrl = await prompt3.ask("og:url (canonical page URL): ");
3539
5270
  const tags = [
3540
5271
  `<meta property="og:type" content="website">`,
3541
5272
  `<meta property="og:title" content="${ogTitle}">`,
@@ -3552,7 +5283,7 @@ async function seoOpenGraph(options) {
3552
5283
  }
3553
5284
  console.log(chalk9.dim("\nTags to add:"));
3554
5285
  tags.forEach((tag) => console.log(chalk9.green(` + ${tag}`)));
3555
- const confirm = await prompt2.ask("\nApply changes? (y/N): ");
5286
+ const confirm = await prompt3.ask("\nApply changes? (y/N): ");
3556
5287
  if (confirm.toLowerCase() === "y") {
3557
5288
  const newContent = content.slice(0, insertPoint.index) + "\n" + tags.map((tag) => insertPoint.indent + tag).join("\n") + content.slice(insertPoint.index);
3558
5289
  await writeFile7(targetFile, newContent);
@@ -3561,11 +5292,11 @@ async function seoOpenGraph(options) {
3561
5292
  console.log(chalk9.dim("Cancelled."));
3562
5293
  }
3563
5294
  } finally {
3564
- prompt2.close();
5295
+ prompt3.close();
3565
5296
  }
3566
5297
  }
3567
5298
  async function seoTwitter(options) {
3568
- const prompt2 = createPrompt3();
5299
+ const prompt3 = createPrompt3();
3569
5300
  try {
3570
5301
  console.log(chalk9.blue("\n\u{1F426} Add Twitter Card Tags\n"));
3571
5302
  let targetFile;
@@ -3584,7 +5315,7 @@ async function seoTwitter(options) {
3584
5315
  if (files.length > 10) {
3585
5316
  console.log(chalk9.dim(` ... and ${files.length - 10} more`));
3586
5317
  }
3587
- const fileChoice = await prompt2.ask("\nEnter file path or number: ");
5318
+ const fileChoice = await prompt3.ask("\nEnter file path or number: ");
3588
5319
  const num = parseInt(fileChoice, 10);
3589
5320
  if (num > 0 && num <= files.length) {
3590
5321
  targetFile = files[num - 1] ?? "";
@@ -3597,10 +5328,10 @@ async function seoTwitter(options) {
3597
5328
  return;
3598
5329
  }
3599
5330
  console.log(chalk9.dim("Card types: summary, summary_large_image, app, player"));
3600
- const cardType = await prompt2.ask("twitter:card type (default: summary_large_image): ") || "summary_large_image";
3601
- const twitterTitle = await prompt2.ask("twitter:title: ");
3602
- const twitterDescription = await prompt2.ask("twitter:description: ");
3603
- const twitterImage = await prompt2.ask("twitter:image (full URL): ");
5331
+ const cardType = await prompt3.ask("twitter:card type (default: summary_large_image): ") || "summary_large_image";
5332
+ const twitterTitle = await prompt3.ask("twitter:title: ");
5333
+ const twitterDescription = await prompt3.ask("twitter:description: ");
5334
+ const twitterImage = await prompt3.ask("twitter:image (full URL): ");
3604
5335
  const tags = [
3605
5336
  `<meta name="twitter:card" content="${cardType}">`,
3606
5337
  `<meta name="twitter:title" content="${twitterTitle}">`,
@@ -3616,7 +5347,7 @@ async function seoTwitter(options) {
3616
5347
  }
3617
5348
  console.log(chalk9.dim("\nTags to add:"));
3618
5349
  tags.forEach((tag) => console.log(chalk9.green(` + ${tag}`)));
3619
- const confirm = await prompt2.ask("\nApply changes? (y/N): ");
5350
+ const confirm = await prompt3.ask("\nApply changes? (y/N): ");
3620
5351
  if (confirm.toLowerCase() === "y") {
3621
5352
  const newContent = content.slice(0, insertPoint.index) + "\n" + tags.map((tag) => insertPoint.indent + tag).join("\n") + content.slice(insertPoint.index);
3622
5353
  await writeFile7(targetFile, newContent);
@@ -3625,7 +5356,7 @@ async function seoTwitter(options) {
3625
5356
  console.log(chalk9.dim("Cancelled."));
3626
5357
  }
3627
5358
  } finally {
3628
- prompt2.close();
5359
+ prompt3.close();
3629
5360
  }
3630
5361
  }
3631
5362
  function createSeoCommand() {
@@ -4176,7 +5907,7 @@ import { createClient as createClient3 } from "@supabase/supabase-js";
4176
5907
  import { readFile as readFile10 } from "fs/promises";
4177
5908
  import { existsSync as existsSync13 } from "fs";
4178
5909
  import { join as join12, extname as extname2 } from "path";
4179
- import { createHash } from "crypto";
5910
+ import { createHash as createHash2 } from "crypto";
4180
5911
  var CHUNK_SIZE2 = 1e3;
4181
5912
  var CHUNK_OVERLAP2 = 200;
4182
5913
  var INDEXABLE_EXTENSIONS2 = /* @__PURE__ */ new Set([
@@ -4290,7 +6021,7 @@ var CloudIndexer = class {
4290
6021
  * Compute file hash for change detection
4291
6022
  */
4292
6023
  async computeFileHash(content) {
4293
- return createHash("sha256").update(content).digest("hex").slice(0, 16);
6024
+ return createHash2("sha256").update(content).digest("hex").slice(0, 16);
4294
6025
  }
4295
6026
  /**
4296
6027
  * Index a single file
@@ -4749,13 +6480,422 @@ async function githubDisconnect() {
4749
6480
  }
4750
6481
  }
4751
6482
 
6483
+ // src/cli/interview.ts
6484
+ import chalk14 from "chalk";
6485
+ import readline2 from "readline";
6486
+ import ora3 from "ora";
6487
+ import { existsSync as existsSync14, readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
6488
+ import { join as join14 } from "path";
6489
+ import { randomUUID } from "crypto";
6490
+ function getConstitutionPath(cwd) {
6491
+ return join14(cwd, ".archon", "constitution.json");
6492
+ }
6493
+ function getDraftPath(cwd) {
6494
+ return join14(cwd, ".archon", "constitution-draft.json");
6495
+ }
6496
+ async function interview(options = {}) {
6497
+ const cwd = process.cwd();
6498
+ const archonDir = join14(cwd, ".archon");
6499
+ if (!existsSync14(archonDir)) {
6500
+ mkdirSync2(archonDir, { recursive: true });
6501
+ }
6502
+ console.log(chalk14.bold("\n\u{1F3AF} ArchonDev Project Interview"));
6503
+ console.log(chalk14.dim("Let's define what you're building.\n"));
6504
+ const constitutionPath = getConstitutionPath(cwd);
6505
+ const draftPath = getDraftPath(cwd);
6506
+ if (existsSync14(constitutionPath)) {
6507
+ const existing = deserializeConstitution(readFileSync3(constitutionPath, "utf-8"));
6508
+ if (existing.state === "FROZEN") {
6509
+ console.log(chalk14.yellow("[!] A frozen Constitution already exists."));
6510
+ console.log(chalk14.dim(` Project: ${existing.branding.projectName}`));
6511
+ console.log(chalk14.dim(` Frozen: ${existing.frozenAt?.toISOString()}`));
6512
+ console.log();
6513
+ const action = await prompt2("Start a new interview (overwrite) or view existing? (new/view)");
6514
+ if (action.toLowerCase() === "view") {
6515
+ console.log("\n" + summarizeConstitution(existing));
6516
+ return;
6517
+ } else if (action.toLowerCase() !== "new") {
6518
+ console.log(chalk14.dim("Cancelled."));
6519
+ return;
6520
+ }
6521
+ }
6522
+ }
6523
+ let constitution;
6524
+ let startPhase = 1;
6525
+ if (existsSync14(draftPath) && options.resume !== false) {
6526
+ const draft = deserializeConstitution(readFileSync3(draftPath, "utf-8"));
6527
+ console.log(chalk14.blue("[i] Found draft from previous session."));
6528
+ console.log(chalk14.dim(` Project: ${draft.branding.projectName || "(unnamed)"}`));
6529
+ console.log();
6530
+ const resumeChoice = await prompt2("Resume draft or start fresh? (resume/fresh)");
6531
+ if (resumeChoice.toLowerCase() === "resume") {
6532
+ constitution = draft;
6533
+ for (let phase = 1; phase <= 5; phase++) {
6534
+ const phaseInfo = INTERVIEW_PHASES[phase];
6535
+ if (!phaseInfo?.isComplete(constitution)) {
6536
+ startPhase = phase;
6537
+ break;
6538
+ }
6539
+ }
6540
+ console.log(chalk14.green(`
6541
+ \u2713 Resuming at Phase ${startPhase}: ${INTERVIEW_PHASES[startPhase]?.name}
6542
+ `));
6543
+ } else {
6544
+ constitution = createDraftConstitution(randomUUID());
6545
+ }
6546
+ } else {
6547
+ constitution = createDraftConstitution(randomUUID());
6548
+ }
6549
+ if (options.phase && options.phase >= 1 && options.phase <= 5) {
6550
+ startPhase = options.phase;
6551
+ console.log(chalk14.dim(`Starting at Phase ${startPhase}...
6552
+ `));
6553
+ }
6554
+ console.log(chalk14.dim("\u2500".repeat(40)));
6555
+ console.log(chalk14.dim(formatProgressBar(startPhase)));
6556
+ console.log(chalk14.dim("\u2500".repeat(40)));
6557
+ console.log();
6558
+ let currentPhase = startPhase;
6559
+ while (currentPhase <= 5) {
6560
+ const result = await runPhase(currentPhase, constitution, cwd);
6561
+ if (result.action === "exit") {
6562
+ saveDraft(cwd, constitution);
6563
+ console.log(chalk14.dim("\nDraft saved. Run `archon interview` to resume.\n"));
6564
+ return;
6565
+ }
6566
+ if (result.action === "back" && currentPhase > 1) {
6567
+ currentPhase = currentPhase - 1;
6568
+ continue;
6569
+ }
6570
+ if (result.constitution) {
6571
+ constitution = result.constitution;
6572
+ }
6573
+ saveDraft(cwd, constitution);
6574
+ const next = getNextPhase(currentPhase);
6575
+ if (next) {
6576
+ currentPhase = next;
6577
+ console.log();
6578
+ console.log(chalk14.dim("\u2500".repeat(40)));
6579
+ console.log(chalk14.dim(formatProgressBar(currentPhase)));
6580
+ console.log(chalk14.dim("\u2500".repeat(40)));
6581
+ console.log();
6582
+ } else {
6583
+ break;
6584
+ }
6585
+ }
6586
+ const validationResult = validateConstitution(constitution);
6587
+ if (!validationResult.valid) {
6588
+ console.log(chalk14.red("\n[!] Constitution has validation errors:\n"));
6589
+ for (const error of validationResult.errors) {
6590
+ console.log(chalk14.red(` \u2022 ${error}`));
6591
+ }
6592
+ console.log();
6593
+ const fix = await promptYesNo2("Would you like to go back and fix these?", true);
6594
+ if (fix) {
6595
+ await interview({ ...options, phase: 1 });
6596
+ return;
6597
+ }
6598
+ }
6599
+ if (validationResult.warnings.length > 0) {
6600
+ console.log(chalk14.yellow("\n[!] Warnings:\n"));
6601
+ for (const warning of validationResult.warnings) {
6602
+ console.log(chalk14.yellow(` \u2022 ${warning}`));
6603
+ }
6604
+ console.log();
6605
+ }
6606
+ const config = await loadConfig();
6607
+ constitution.complexity = calculateComplexity(constitution);
6608
+ constitution.estimatedBuildHours = estimateBuildHours(constitution.complexity);
6609
+ constitution.costs = estimateCosts(constitution, config.tier || "FREE");
6610
+ const challengeResult = analyzeForChallenges(constitution);
6611
+ if (challengeResult.shouldChallenge) {
6612
+ console.log(chalk14.bold("\n\u{1F3AF} Challenge Mode\n"));
6613
+ console.log(chalk14.dim("Let me share some observations about your project scope...\n"));
6614
+ for (const challenge of challengeResult.challenges) {
6615
+ const prompt3 = generateChallengePrompt(challenge);
6616
+ const icon = challenge.severity === "critical" ? chalk14.red("\u25CF") : chalk14.yellow("\u25CF");
6617
+ console.log(`${icon} ${chalk14.bold(challenge.title)}`);
6618
+ console.log(chalk14.dim(` ${prompt3}`));
6619
+ console.log();
6620
+ }
6621
+ console.log(chalk14.dim("\u2500".repeat(40)));
6622
+ console.log(summarizeChallenges(challengeResult));
6623
+ console.log(chalk14.dim("\u2500".repeat(40)));
6624
+ console.log();
6625
+ if (challengeResult.featuresToDefer.length > 0) {
6626
+ console.log(chalk14.bold("Suggested Features to Defer to Post-MVP:\n"));
6627
+ for (const feature of challengeResult.featuresToDefer) {
6628
+ console.log(` \u2022 ${feature.name}`);
6629
+ }
6630
+ console.log();
6631
+ const acceptDeferrals = await promptYesNo2(
6632
+ `Move ${challengeResult.featuresToDefer.length} feature(s) to post-MVP?`,
6633
+ true
6634
+ );
6635
+ if (acceptDeferrals) {
6636
+ const featureIds = challengeResult.featuresToDefer.map((f) => f.id);
6637
+ constitution = applyDeferrals(constitution, featureIds);
6638
+ constitution.complexity = calculateComplexity(constitution);
6639
+ constitution.estimatedBuildHours = estimateBuildHours(constitution.complexity);
6640
+ constitution.costs = estimateCosts(constitution, config.tier || "FREE");
6641
+ console.log(chalk14.green("\n\u2713 Features deferred. Updated estimates:"));
6642
+ console.log(chalk14.dim(` Complexity: ${constitution.complexity.tier}`));
6643
+ console.log(chalk14.dim(` Build time: ~${Math.round(constitution.estimatedBuildHours)} hours`));
6644
+ console.log();
6645
+ saveDraft(cwd, constitution);
6646
+ }
6647
+ }
6648
+ const criticalCount = challengeResult.challenges.filter((c) => c.severity === "critical").length;
6649
+ if (criticalCount > 0) {
6650
+ console.log(chalk14.red(`
6651
+ \u26A0\uFE0F ${criticalCount} critical issue(s) detected.`));
6652
+ const proceed = await promptYesNo2("Proceed anyway?", false);
6653
+ if (!proceed) {
6654
+ saveDraft(cwd, constitution);
6655
+ console.log(chalk14.dim("\nDraft saved. Address the issues and run `archon interview` again.\n"));
6656
+ return;
6657
+ }
6658
+ }
6659
+ } else {
6660
+ console.log(chalk14.green("\n\u2713 Scope looks reasonable! No major concerns detected.\n"));
6661
+ }
6662
+ console.log(chalk14.bold("\n\u{1F4CB} Constitution Summary\n"));
6663
+ console.log(summarizeConstitution(constitution));
6664
+ console.log();
6665
+ if (options.dryRun) {
6666
+ console.log(chalk14.dim("(Dry run mode - not freezing Constitution)"));
6667
+ saveDraft(cwd, constitution);
6668
+ return;
6669
+ }
6670
+ const confirmFreeze = await promptYesNo2("Freeze this Constitution and start building?", true);
6671
+ if (!confirmFreeze) {
6672
+ saveDraft(cwd, constitution);
6673
+ console.log(chalk14.dim("\nDraft saved. Run `archon interview` to continue.\n"));
6674
+ return;
6675
+ }
6676
+ const spinner = ora3("Freezing Constitution...").start();
6677
+ try {
6678
+ const frozen = freezeConstitution(constitution);
6679
+ writeFileSync(constitutionPath, serializeConstitution(frozen));
6680
+ if (existsSync14(draftPath)) {
6681
+ const { unlinkSync } = await import("fs");
6682
+ unlinkSync(draftPath);
6683
+ }
6684
+ spinner.succeed(chalk14.green("Constitution frozen!"));
6685
+ console.log();
6686
+ console.log(chalk14.dim(`Hash: ${frozen.hash?.substring(0, 32)}...`));
6687
+ console.log(chalk14.dim(`Saved: ${constitutionPath}`));
6688
+ console.log();
6689
+ console.log(chalk14.bold("Next Steps:\n"));
6690
+ console.log(` ${chalk14.cyan("1.")} Run ${chalk14.bold("archon generate")} to create atoms from this Constitution`);
6691
+ console.log(` ${chalk14.cyan("2.")} Run ${chalk14.bold("archon list")} to see generated atoms`);
6692
+ console.log(` ${chalk14.cyan("3.")} Run ${chalk14.bold("archon execute <atom-id>")} to start building`);
6693
+ console.log();
6694
+ } catch (err) {
6695
+ spinner.fail("Failed to freeze Constitution");
6696
+ console.error(err);
6697
+ }
6698
+ }
6699
+ async function runPhase(phase, constitution, cwd) {
6700
+ const phaseInfo = INTERVIEW_PHASES[phase];
6701
+ console.log(chalk14.bold(`Phase ${phase}: ${phaseInfo.name}`));
6702
+ console.log(chalk14.dim(phaseInfo.description));
6703
+ console.log();
6704
+ const skippable = getSkippableQuestions(phase, constitution);
6705
+ let updatedConstitution = { ...constitution };
6706
+ for (const question of phaseInfo.questions) {
6707
+ if (skippable.includes(question.id)) {
6708
+ console.log(chalk14.dim(`[\u2713] ${question.prompt.split("?")[0]}... (already answered)`));
6709
+ continue;
6710
+ }
6711
+ if (phase === 5 && question.id === "review_summary") {
6712
+ console.log(chalk14.bold("\n\u{1F4CB} Current State:\n"));
6713
+ console.log(summarizeConstitution(updatedConstitution));
6714
+ console.log();
6715
+ }
6716
+ const answer = await promptQuestion(question);
6717
+ if (answer.toLowerCase() === "exit" || answer.toLowerCase() === "quit") {
6718
+ return { action: "exit", constitution: updatedConstitution };
6719
+ }
6720
+ if (answer.toLowerCase() === "back") {
6721
+ return { action: "back", constitution: updatedConstitution };
6722
+ }
6723
+ if (answer.toLowerCase() === "skip" && !question.required) {
6724
+ continue;
6725
+ }
6726
+ if (question.validator) {
6727
+ const validation = question.validator(answer);
6728
+ if (!validation.valid) {
6729
+ console.log(chalk14.red(` ${validation.error}`));
6730
+ continue;
6731
+ }
6732
+ }
6733
+ const extracted = question.extractor(answer, updatedConstitution);
6734
+ updatedConstitution = { ...updatedConstitution, ...extracted };
6735
+ if (question.followUp && answer.length > 10) {
6736
+ console.log(chalk14.dim(` ${question.followUp}`));
6737
+ }
6738
+ }
6739
+ return { action: "next", constitution: updatedConstitution };
6740
+ }
6741
+ function saveDraft(cwd, constitution) {
6742
+ const draftPath = getDraftPath(cwd);
6743
+ writeFileSync(draftPath, serializeConstitution(constitution));
6744
+ }
6745
+ async function promptQuestion(question) {
6746
+ const prefix = question.required ? chalk14.red("*") : " ";
6747
+ return prompt2(`${prefix} ${question.prompt}`);
6748
+ }
6749
+ function prompt2(question) {
6750
+ return new Promise((resolve) => {
6751
+ const rl = readline2.createInterface({
6752
+ input: process.stdin,
6753
+ output: process.stdout
6754
+ });
6755
+ rl.question(`${chalk14.cyan("?")} ${question}
6756
+ > `, (answer) => {
6757
+ rl.close();
6758
+ resolve(answer.trim());
6759
+ });
6760
+ });
6761
+ }
6762
+ function promptYesNo2(question, defaultValue) {
6763
+ return new Promise((resolve) => {
6764
+ const rl = readline2.createInterface({
6765
+ input: process.stdin,
6766
+ output: process.stdout
6767
+ });
6768
+ const hint = defaultValue ? "(Y/n)" : "(y/N)";
6769
+ rl.question(`${chalk14.cyan("?")} ${question} ${hint}: `, (answer) => {
6770
+ rl.close();
6771
+ if (answer.trim() === "") {
6772
+ resolve(defaultValue);
6773
+ } else {
6774
+ resolve(answer.toLowerCase().startsWith("y"));
6775
+ }
6776
+ });
6777
+ });
6778
+ }
6779
+ async function showConstitution() {
6780
+ const cwd = process.cwd();
6781
+ const constitutionPath = getConstitutionPath(cwd);
6782
+ const draftPath = getDraftPath(cwd);
6783
+ if (existsSync14(constitutionPath)) {
6784
+ const constitution = deserializeConstitution(readFileSync3(constitutionPath, "utf-8"));
6785
+ console.log(summarizeConstitution(constitution));
6786
+ } else if (existsSync14(draftPath)) {
6787
+ const draft = deserializeConstitution(readFileSync3(draftPath, "utf-8"));
6788
+ console.log(chalk14.yellow("[DRAFT]"));
6789
+ console.log(summarizeConstitution(draft));
6790
+ } else {
6791
+ console.log(chalk14.dim("No Constitution found. Run `archon interview` to create one."));
6792
+ }
6793
+ }
6794
+ async function validateConstitutionCommand() {
6795
+ const cwd = process.cwd();
6796
+ const constitutionPath = getConstitutionPath(cwd);
6797
+ const draftPath = getDraftPath(cwd);
6798
+ const path2 = existsSync14(constitutionPath) ? constitutionPath : draftPath;
6799
+ if (!existsSync14(path2)) {
6800
+ console.log(chalk14.dim("No Constitution found. Run `archon interview` to create one."));
6801
+ return;
6802
+ }
6803
+ const constitution = deserializeConstitution(readFileSync3(path2, "utf-8"));
6804
+ const result = validateConstitution(constitution);
6805
+ if (result.valid) {
6806
+ console.log(chalk14.green("\u2713 Constitution is valid"));
6807
+ } else {
6808
+ console.log(chalk14.red("\u2717 Constitution has errors:"));
6809
+ for (const error of result.errors) {
6810
+ console.log(chalk14.red(` \u2022 ${error}`));
6811
+ }
6812
+ }
6813
+ if (result.warnings.length > 0) {
6814
+ console.log(chalk14.yellow("\nWarnings:"));
6815
+ for (const warning of result.warnings) {
6816
+ console.log(chalk14.yellow(` \u2022 ${warning}`));
6817
+ }
6818
+ }
6819
+ }
6820
+ async function exportConstitution(format) {
6821
+ const cwd = process.cwd();
6822
+ const constitutionPath = getConstitutionPath(cwd);
6823
+ if (!existsSync14(constitutionPath)) {
6824
+ console.log(chalk14.dim("No frozen Constitution found."));
6825
+ return;
6826
+ }
6827
+ const constitution = deserializeConstitution(readFileSync3(constitutionPath, "utf-8"));
6828
+ switch (format.toLowerCase()) {
6829
+ case "json":
6830
+ console.log(serializeConstitution(constitution));
6831
+ break;
6832
+ case "markdown":
6833
+ case "md":
6834
+ console.log(summarizeConstitution(constitution));
6835
+ break;
6836
+ default:
6837
+ console.log(chalk14.red(`Unknown format: ${format}. Use 'json' or 'markdown'.`));
6838
+ }
6839
+ }
6840
+ async function generateAtoms(options = {}) {
6841
+ const cwd = process.cwd();
6842
+ const constitutionPath = getConstitutionPath(cwd);
6843
+ if (!existsSync14(constitutionPath)) {
6844
+ console.log(chalk14.red("No frozen Constitution found."));
6845
+ console.log(chalk14.dim("Run `archon interview` to create one first."));
6846
+ return;
6847
+ }
6848
+ const constitution = deserializeConstitution(readFileSync3(constitutionPath, "utf-8"));
6849
+ if (constitution.state !== "FROZEN") {
6850
+ console.log(chalk14.yellow("Constitution is not frozen yet."));
6851
+ console.log(chalk14.dim("Complete the interview and freeze before generating atoms."));
6852
+ return;
6853
+ }
6854
+ console.log(chalk14.bold("\n\u{1F527} Generating Atoms from Constitution\n"));
6855
+ console.log(chalk14.dim(`Project: ${constitution.branding.projectName}`));
6856
+ console.log(chalk14.dim(`Hash: ${constitution.hash?.substring(0, 16)}...`));
6857
+ console.log();
6858
+ const spinner = ora3("Generating atoms...").start();
6859
+ const result = generateAtomsFromConstitution(constitution, {
6860
+ includePostMvp: options.includePostMvp,
6861
+ addSetupAtom: true,
6862
+ addTestingAtom: true
6863
+ });
6864
+ spinner.succeed(`Generated ${result.totalAtoms} atom(s)`);
6865
+ console.log();
6866
+ console.log(summarizeGeneration(result));
6867
+ console.log();
6868
+ if (options.dryRun) {
6869
+ console.log(chalk14.dim("(Dry run - not writing prd.json)"));
6870
+ return;
6871
+ }
6872
+ const prdPath = options.output ?? join14(cwd, "prd.json");
6873
+ const prdContent = formatAsPrdJson(result, constitution);
6874
+ if (existsSync14(prdPath)) {
6875
+ const overwrite = await promptYesNo2("prd.json already exists. Overwrite?", false);
6876
+ if (!overwrite) {
6877
+ console.log(chalk14.dim("Cancelled. Existing prd.json preserved."));
6878
+ return;
6879
+ }
6880
+ }
6881
+ writeFileSync(prdPath, JSON.stringify(prdContent, null, 2));
6882
+ console.log(chalk14.green(`
6883
+ \u2713 Written to ${prdPath}`));
6884
+ console.log();
6885
+ console.log(chalk14.bold("Next Steps:\n"));
6886
+ console.log(` ${chalk14.cyan("1.")} Run ${chalk14.bold("archon list")} to see all atoms`);
6887
+ console.log(` ${chalk14.cyan("2.")} Run ${chalk14.bold("archon execute ATOM-001")} to start building`);
6888
+ console.log(` ${chalk14.cyan("3.")} Run ${chalk14.bold("archon watch")} to monitor progress`);
6889
+ console.log();
6890
+ }
6891
+
4752
6892
  // src/cli/index.ts
4753
6893
  var program = new Command4();
4754
6894
  program.name("archon").description("Local-first AI-powered development governance").version("1.1.0").action(async () => {
4755
6895
  const cwd = process.cwd();
4756
6896
  const wasInitialized = isInitialized(cwd);
4757
6897
  if (!wasInitialized) {
4758
- console.log(chalk14.blue("\nArchonDev is not initialized in this folder.\n"));
6898
+ console.log(chalk15.blue("\nArchonDev is not initialized in this folder.\n"));
4759
6899
  await init({ analyze: true, git: true });
4760
6900
  }
4761
6901
  await start({ skipGovernanceBanner: !wasInitialized });
@@ -4763,7 +6903,7 @@ program.name("archon").description("Local-first AI-powered development governanc
4763
6903
  program.command("login").description("Authenticate with ArchonDev").option("-p, --provider <provider>", "OAuth provider (github or google)", "github").action(async (options) => {
4764
6904
  const provider = options.provider;
4765
6905
  if (provider !== "github" && provider !== "google") {
4766
- console.error(chalk14.red('Invalid provider. Use "github" or "google"'));
6906
+ console.error(chalk15.red('Invalid provider. Use "github" or "google"'));
4767
6907
  process.exit(1);
4768
6908
  }
4769
6909
  await login(provider);
@@ -4948,10 +7088,29 @@ cleanupCmd.command("check").description("Analyze workspace for bloat and mainten
4948
7088
  cleanupCmd.command("run").description("Execute cleanup (archive old entries, remove stale files)").action(cleanupRun);
4949
7089
  cleanupCmd.command("auto").description("Enable/disable automatic cleanup checks").argument("[action]", "enable, disable, or status", "status").action(async (action) => {
4950
7090
  if (action !== "enable" && action !== "disable" && action !== "status") {
4951
- console.error(chalk14.red("Invalid action. Use: enable, disable, or status"));
7091
+ console.error(chalk15.red("Invalid action. Use: enable, disable, or status"));
4952
7092
  process.exit(1);
4953
7093
  }
4954
7094
  await cleanupAuto(action);
4955
7095
  });
4956
7096
  cleanupCmd.action(cleanupCheck);
7097
+ var interviewCmd = program.command("interview").description("5-phase structured interview to define your project");
7098
+ interviewCmd.command("start").description("Start or resume the project interview").option("-p, --phase <number>", "Start at a specific phase (1-5)").option("--dry-run", "Generate Constitution without freezing").action(async (options) => {
7099
+ await interview({
7100
+ phase: options.phase ? parseInt(options.phase, 10) : void 0,
7101
+ dryRun: options.dryRun
7102
+ });
7103
+ });
7104
+ interviewCmd.command("show").description("Show current Constitution").action(showConstitution);
7105
+ interviewCmd.command("validate").description("Validate current Constitution").action(validateConstitutionCommand);
7106
+ interviewCmd.command("export").description("Export Constitution to different formats").argument("<format>", "Output format (json or markdown)").action(exportConstitution);
7107
+ interviewCmd.command("generate").description("Generate atoms (prd.json) from frozen Constitution").option("--include-post-mvp", "Include POST_MVP features").option("--dry-run", "Show what would be generated without writing files").option("-o, --output <path>", "Output path for prd.json").action(async (options) => {
7108
+ await generateAtoms(options);
7109
+ });
7110
+ interviewCmd.action(async () => {
7111
+ await interview();
7112
+ });
7113
+ program.command("generate").description("Generate atoms from Constitution (alias for interview generate)").option("--include-post-mvp", "Include POST_MVP features").option("--dry-run", "Show what would be generated without writing files").option("-o, --output <path>", "Output path for prd.json").action(async (options) => {
7114
+ await generateAtoms(options);
7115
+ });
4957
7116
  program.parse();