archondev 2.1.6 → 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.
package/dist/index.js CHANGED
@@ -1,4 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ listModels,
4
+ resetPreferences,
5
+ setExecutionPreference,
6
+ setPreference,
7
+ showExecutionPreferences,
8
+ showPreferences
9
+ } from "./chunk-XP7PNLXG.js";
10
+ import {
11
+ parallelClean,
12
+ parallelMerge,
13
+ parallelStatus
14
+ } from "./chunk-OI4K3RYO.js";
2
15
  import {
3
16
  DependencyParser,
4
17
  EnvironmentConfigLoader,
@@ -7,19 +20,19 @@ import {
7
20
  cloudLogs,
8
21
  cloudStatus,
9
22
  execute
10
- } from "./chunk-3NPZQOK2.js";
23
+ } from "./chunk-HGO4UUAC.js";
11
24
  import {
12
25
  list
13
- } from "./chunk-TEY4GCMH.js";
26
+ } from "./chunk-7FJ4ATJE.js";
14
27
  import {
15
28
  bugReport
16
- } from "./chunk-KF6MFAB4.js";
29
+ } from "./chunk-GLBVZOBA.js";
17
30
  import {
18
31
  addKey,
19
32
  listKeys,
20
33
  removeKey,
21
34
  setPrimaryKey
22
- } from "./chunk-SSSEOM25.js";
35
+ } from "./chunk-BBAUT4M5.js";
23
36
  import {
24
37
  reviewAnalyze,
25
38
  reviewExport,
@@ -33,19 +46,12 @@ import {
33
46
  reviewUpdate
34
47
  } from "./chunk-QSYKKPFF.js";
35
48
  import "./chunk-VKM3HAHW.js";
36
- import {
37
- listModels,
38
- resetPreferences,
39
- setExecutionPreference,
40
- setPreference,
41
- showExecutionPreferences,
42
- showPreferences
43
- } from "./chunk-F3RLDQTQ.js";
44
49
  import {
45
50
  login,
46
51
  logout,
47
52
  status
48
- } from "./chunk-UI4UQ24R.js";
53
+ } from "./chunk-2BPIPDFV.js";
54
+ import "./chunk-C5TDNTNC.js";
49
55
  import {
50
56
  API_URL,
51
57
  SUPABASE_ANON_KEY,
@@ -55,25 +61,20 @@ import {
55
61
  init,
56
62
  isInitialized
57
63
  } from "./chunk-P666JE3G.js";
58
- import {
59
- parallelClean,
60
- parallelMerge,
61
- parallelStatus
62
- } from "./chunk-TSSFMB6E.js";
63
64
  import {
64
65
  listLocalAtoms,
65
66
  loadAtom,
66
67
  plan
67
- } from "./chunk-RCW22YNI.js";
68
+ } from "./chunk-5HVYNCLT.js";
68
69
  import {
69
70
  ArchitectAgent
70
71
  } from "./chunk-5IQKC2TD.js";
71
- import "./chunk-A7QU6JC6.js";
72
72
  import "./chunk-SMR7JQK6.js";
73
+ import "./chunk-A7QU6JC6.js";
73
74
  import {
74
75
  getAuthToken,
75
76
  loadConfig
76
- } from "./chunk-IVY5AHPS.js";
77
+ } from "./chunk-Y7DQ5XTU.js";
77
78
  import {
78
79
  ArchitectureParser
79
80
  } from "./chunk-5EVHUDQX.js";
@@ -81,7 +82,7 @@ import "./chunk-QGM4M3NI.js";
81
82
 
82
83
  // src/cli/index.ts
83
84
  import { Command as Command4 } from "commander";
84
- import chalk14 from "chalk";
85
+ import chalk15 from "chalk";
85
86
  import "dotenv/config";
86
87
 
87
88
  // src/cli/promote.ts
@@ -839,34 +840,1628 @@ async function cleanupAuto(action) {
839
840
  console.log(chalk3.green("\n\u2713 Auto cleanup disabled\n"));
840
841
  }
841
842
  }
842
- async function shouldRunAutoCleanup(cwd) {
843
- const config = await loadCleanupConfig(cwd);
844
- 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
+ }
845
2376
  }
846
- async function runAutoCleanupCheck(cwd) {
847
- const progressPath = join3(cwd, PROGRESS_FILE);
848
- const archonPath = join3(cwd, ARCHON_DIR);
849
- const config = await loadCleanupConfig(cwd);
850
- const progressMaxKb = config?.progressMaxKb ?? DEFAULT_THRESHOLDS.progressMaxKb;
851
- const archonDirMaxMb = config?.archonDirMaxMb ?? DEFAULT_THRESHOLDS.archonDirMaxMb;
852
- let needsAttention = false;
853
- if (existsSync3(progressPath)) {
854
- const size = statSync(progressPath).size;
855
- if (size > progressMaxKb * 1024) {
856
- 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, []);
857
2446
  }
2447
+ byFeature.get(key)?.push(atom);
858
2448
  }
859
- if (existsSync3(archonPath)) {
860
- const size = getDirSize(archonPath);
861
- if (size > archonDirMaxMb * 1024 * 1024) {
862
- 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}`);
863
2455
  }
864
2456
  }
865
- const orphaned = getOrphanedWorktrees(cwd);
866
- if (orphaned.length > 0) {
867
- 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
+ }
868
2463
  }
869
- return needsAttention;
2464
+ return lines.join("\n");
870
2465
  }
871
2466
 
872
2467
  // src/cli/start.ts
@@ -876,7 +2471,8 @@ async function start(options = {}) {
876
2471
  console.log(chalk4.bold("\nArchonDev - AI-Powered Development Governance"));
877
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"));
878
2473
  }
879
- const token = await getAuthToken();
2474
+ const config = await loadConfig();
2475
+ const token = getAuthToken(config);
880
2476
  if (!token) {
881
2477
  console.log(chalk4.yellow("[!] Not logged in"));
882
2478
  const shouldLogin = await promptYesNo("Would you like to login now?", true);
@@ -1066,100 +2662,252 @@ async function displayGovernanceBanner(status2) {
1066
2662
  }
1067
2663
  console.log();
1068
2664
  }
1069
- async function handleNewProject(cwd, state) {
1070
- console.log(chalk4.bold("Starting a new project? Great!\n"));
1071
- console.log(chalk4.dim("I'll ask you a few quick questions to set things up right."));
1072
- 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) {
1073
2719
  console.log(chalk4.bold("What would you like to do?\n"));
1074
2720
  console.log(` ${chalk4.cyan("1")}) ${chalk4.bold("Start interview")} \u2014 I'll ask questions to understand your project`);
1075
2721
  console.log(` ${chalk4.cyan("2")}) ${chalk4.bold("Quick start")} \u2014 Just create basic governance files`);
1076
- 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`);
1077
2723
  console.log(` ${chalk4.cyan("q")}) ${chalk4.dim("Quit")}`);
1078
2724
  console.log();
1079
2725
  const choice = await prompt("Enter choice");
1080
2726
  switch (choice.toLowerCase()) {
1081
2727
  case "1":
1082
- await runNewProjectInterview(cwd);
2728
+ await runConversationalInterview(cwd, "");
1083
2729
  break;
1084
2730
  case "2":
1085
2731
  await quickStart(cwd);
1086
2732
  break;
1087
- case "3":
1088
- console.log(chalk4.yellow("\nTemplates coming soon! Using quick start for now.\n"));
1089
- 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
+ }
1090
2739
  break;
2740
+ }
1091
2741
  case "q":
1092
2742
  process.exit(0);
1093
2743
  default:
1094
2744
  console.log(chalk4.yellow("Invalid choice. Please try again."));
1095
- await handleNewProject(cwd, state);
1096
- }
1097
- }
1098
- async function runNewProjectInterview(cwd) {
1099
- console.log(chalk4.blue("\n-- Project Interview --\n"));
1100
- console.log(chalk4.bold("Phase 1: The Vision\n"));
1101
- const projectName = await prompt("What's the project name?");
1102
- const projectDescription = await prompt("In one sentence, what does this project do?");
1103
- const audience = await promptChoice("Who is it for?", [
1104
- { key: "1", label: "Just me (personal project)" },
1105
- { key: "2", label: "My team (internal tool)" },
1106
- { key: "3", label: "End users (product)" }
1107
- ]);
1108
- const experience = await promptChoice("Your experience level with this tech stack?", [
1109
- { key: "1", label: "\u{1F7E2} Expert \u2014 I know this well" },
1110
- { key: "2", label: "\u{1F7E1} Intermediate \u2014 I've done similar work" },
1111
- { key: "3", label: "\u{1F534} Learning \u2014 This is new to me" }
1112
- ]);
1113
- console.log(chalk4.bold("\nPhase 2: Tech Stack\n"));
1114
- const language = await promptChoice("Primary language/framework?", [
1115
- { key: "1", label: "TypeScript / JavaScript" },
1116
- { key: "2", label: "Python" },
1117
- { key: "3", label: "Go" },
1118
- { key: "4", label: "Rust" },
1119
- { key: "5", label: "Other" }
1120
- ]);
1121
- const projectType = await promptChoice("Project type?", [
1122
- { key: "1", label: "Frontend only (web UI)" },
1123
- { key: "2", label: "Backend only (API, CLI, service)" },
1124
- { key: "3", label: "Full-stack (both)" },
1125
- { key: "4", label: "Library/package" }
1126
- ]);
1127
- console.log(chalk4.bold("\nPhase 3: Preferences ") + chalk4.dim("(press Enter to skip)\n"));
1128
- const protectedFiles = await prompt("Any files AI should NEVER modify without asking? (comma-separated)");
1129
- const noNoPatterns = await prompt('Anything AI should NEVER do? (e.g., "no console.log")');
1130
- 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";
1131
2869
  const { init: init2 } = await import("./init-6EXMDCWC.js");
1132
- await init2({ analyze: false, git: true });
2870
+ await init2({ analyze: false, git: true, posture });
1133
2871
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1134
2872
  const progressEntry = `
1135
2873
  ## ${today} - Project Initialized via Interview
1136
2874
 
1137
2875
  ### Vision
1138
- - **Name:** ${projectName || "Unnamed Project"}
1139
- - **Description:** ${projectDescription || "No description provided"}
1140
- - **Audience:** ${["Personal", "Team", "End Users"][parseInt(audience) - 1] || "Not specified"}
1141
- - **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"}
1142
2880
 
1143
2881
  ### Stack
1144
- - **Language:** ${["TypeScript/JavaScript", "Python", "Go", "Rust", "Other"][parseInt(language) - 1] || "Not specified"}
1145
- - **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"}
1146
2886
 
1147
2887
  ### Preferences
1148
- ${protectedFiles ? `- **Protected files:** ${protectedFiles}` : "- No protected files specified"}
1149
- ${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"}
1150
2890
 
1151
2891
  ### Files Created
1152
- - ARCHITECTURE.md
2892
+ - ARCHITECTURE.md (posture: ${posture})
1153
2893
  - .archon/config.yaml
1154
2894
  - progress.txt
1155
2895
  `;
1156
2896
  const progressPath = join4(cwd, "progress.txt");
1157
2897
  if (!existsSync4(progressPath)) {
1158
- const { writeFileSync } = await import("fs");
1159
- 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");
1160
2900
  }
1161
2901
  appendFileSync(progressPath, progressEntry);
1162
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
+ }
1163
2911
  console.log(chalk4.bold("Next steps:"));
1164
2912
  console.log(` 1. ${chalk4.cyan("Review")} ARCHITECTURE.md and customize if needed`);
1165
2913
  console.log(` 2. ${chalk4.cyan("Run")} ${chalk4.dim('archon plan "your first task"')} to create an atom`);
@@ -1168,7 +2916,7 @@ ${noNoPatterns ? `- **Forbidden patterns:** ${noNoPatterns}` : "- No forbidden p
1168
2916
  if (continueChoice) {
1169
2917
  const description = await prompt("Describe what you want to build first");
1170
2918
  if (description.trim()) {
1171
- const { plan: plan2 } = await import("./plan-W2UXAR6E.js");
2919
+ const { plan: plan2 } = await import("./plan-TVTKS655.js");
1172
2920
  await plan2(description, {});
1173
2921
  }
1174
2922
  }
@@ -1180,8 +2928,8 @@ async function quickStart(cwd) {
1180
2928
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1181
2929
  const progressPath = join4(cwd, "progress.txt");
1182
2930
  if (!existsSync4(progressPath)) {
1183
- const { writeFileSync } = await import("fs");
1184
- writeFileSync(progressPath, `# ArchonDev Progress Log
2931
+ const { writeFileSync: writeFileSync2 } = await import("fs");
2932
+ writeFileSync2(progressPath, `# ArchonDev Progress Log
1185
2933
 
1186
2934
  This file tracks learnings and decisions across sessions.
1187
2935
 
@@ -1239,8 +2987,8 @@ async function analyzeAndAdapt(cwd) {
1239
2987
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1240
2988
  const progressPath = join4(cwd, "progress.txt");
1241
2989
  if (!existsSync4(progressPath)) {
1242
- const { writeFileSync } = await import("fs");
1243
- 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");
1244
2992
  }
1245
2993
  appendFileSync(progressPath, `
1246
2994
  ## ${today} - ArchonDev Adapted to Existing Project
@@ -1292,8 +3040,8 @@ async function quickAdapt(cwd) {
1292
3040
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1293
3041
  const progressPath = join4(cwd, "progress.txt");
1294
3042
  if (!existsSync4(progressPath)) {
1295
- const { writeFileSync } = await import("fs");
1296
- 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");
1297
3045
  }
1298
3046
  appendFileSync(progressPath, `
1299
3047
  ## ${today} - Quick Adapt (Defaults)
@@ -1389,20 +3137,20 @@ async function showReviewProgress(cwd) {
1389
3137
  }
1390
3138
  }
1391
3139
  async function planTask() {
1392
- const { plan: plan2 } = await import("./plan-W2UXAR6E.js");
3140
+ const { plan: plan2 } = await import("./plan-TVTKS655.js");
1393
3141
  const description = await prompt("Describe what you want to build");
1394
3142
  if (description.trim()) {
1395
3143
  await plan2(description, {});
1396
3144
  }
1397
3145
  }
1398
3146
  async function listAtoms() {
1399
- const { list: list2 } = await import("./list-LEFAISNL.js");
3147
+ const { list: list2 } = await import("./list-XZ42CNFC.js");
1400
3148
  await list2({});
1401
3149
  }
1402
3150
  async function executeNext() {
1403
- const { listLocalAtoms: listLocalAtoms2 } = await import("./plan-W2UXAR6E.js");
3151
+ const { listLocalAtoms: listLocalAtoms2 } = await import("./plan-TVTKS655.js");
1404
3152
  const { analyzeProject, getComplexityDescription, getModeDescription } = await import("./orchestration-X6LHSHBJ.js");
1405
- const { loadExecutionPreferences } = await import("./preferences-3Z4OCNTT.js");
3153
+ const { loadExecutionPreferences } = await import("./preferences-VY6WPI6V.js");
1406
3154
  const cwd = process.cwd();
1407
3155
  const atoms = await listLocalAtoms2();
1408
3156
  const pendingAtoms = atoms.filter((a) => a.status === "READY" || a.status === "IN_PROGRESS");
@@ -1435,25 +3183,25 @@ async function executeNext() {
1435
3183
  const atomId = await prompt("Enter atom ID to execute (or press Enter for first pending)");
1436
3184
  const targetId = atomId.trim() || pendingAtoms[0]?.id;
1437
3185
  if (targetId) {
1438
- const { execute: execute2 } = await import("./execute-5ZSLSWPZ.js");
3186
+ const { execute: execute2 } = await import("./execute-6D6USH33.js");
1439
3187
  await execute2(targetId, {});
1440
3188
  } else {
1441
3189
  console.log(chalk4.yellow("No atom to execute."));
1442
3190
  }
1443
3191
  }
1444
3192
  async function reportBug() {
1445
- const { bugReport: bugReport2 } = await import("./bug-K4V357B2.js");
3193
+ const { bugReport: bugReport2 } = await import("./bug-IT4C6HIG.js");
1446
3194
  const title = await prompt("Bug title");
1447
3195
  if (title.trim()) {
1448
3196
  await bugReport2(title, {});
1449
3197
  }
1450
3198
  }
1451
3199
  async function viewStatus() {
1452
- const { status: status2 } = await import("./auth-COINREKK.js");
3200
+ const { status: status2 } = await import("./auth-R6G5RDJE.js");
1453
3201
  await status2();
1454
3202
  }
1455
3203
  async function settingsMenu() {
1456
- const { interactiveSettings } = await import("./preferences-3Z4OCNTT.js");
3204
+ const { interactiveSettings } = await import("./preferences-VY6WPI6V.js");
1457
3205
  await interactiveSettings();
1458
3206
  await showMainMenu();
1459
3207
  }
@@ -1538,22 +3286,6 @@ function promptYesNo(question, defaultValue) {
1538
3286
  });
1539
3287
  });
1540
3288
  }
1541
- function promptChoice(question, options) {
1542
- return new Promise((resolve) => {
1543
- console.log(`${chalk4.cyan("?")} ${question}`);
1544
- for (const opt of options) {
1545
- console.log(` ${chalk4.dim(opt.key)}) ${opt.label}`);
1546
- }
1547
- const rl = readline.createInterface({
1548
- input: process.stdin,
1549
- output: process.stdout
1550
- });
1551
- rl.question(` ${chalk4.dim("Enter choice")}: `, (answer) => {
1552
- rl.close();
1553
- resolve(answer.trim() || "1");
1554
- });
1555
- });
1556
- }
1557
3289
 
1558
3290
  // src/cli/credits.ts
1559
3291
  import chalk5 from "chalk";
@@ -1612,7 +3344,7 @@ async function addCredits(options = {}) {
1612
3344
  spinner.fail("Not logged in. Run: archon login");
1613
3345
  return;
1614
3346
  }
1615
- const amountDollars = options.amount ? parseFloat(options.amount) : 10;
3347
+ const amountDollars = options.amount ? parseFloat(options.amount) : 5;
1616
3348
  if (isNaN(amountDollars) || amountDollars < 5) {
1617
3349
  spinner.fail("Minimum purchase is $5.00");
1618
3350
  return;
@@ -2573,7 +4305,7 @@ async function a11yCheck(options) {
2573
4305
  console.log(chalk7.dim(`Full report saved to: .archon/a11y-report.json`));
2574
4306
  }
2575
4307
  async function a11yFix(options) {
2576
- const prompt2 = createPrompt();
4308
+ const prompt3 = createPrompt();
2577
4309
  try {
2578
4310
  console.log(chalk7.blue("\n\u267F Accessibility Auto-Fix\n"));
2579
4311
  const reportPath = join6(process.cwd(), ".archon/a11y-report.json");
@@ -2650,7 +4382,7 @@ ${totalFixes} fixes would be applied. Run without --dry-run to apply.`));
2650
4382
  console.log(chalk7.dim('Run "archon a11y check" to verify fixes.'));
2651
4383
  }
2652
4384
  } finally {
2653
- prompt2.close();
4385
+ prompt3.close();
2654
4386
  }
2655
4387
  }
2656
4388
  async function a11yBadge(options) {
@@ -2688,7 +4420,7 @@ async function a11yBadge(options) {
2688
4420
  }
2689
4421
  }
2690
4422
  async function a11yPreDeploy() {
2691
- const prompt2 = createPrompt();
4423
+ const prompt3 = createPrompt();
2692
4424
  try {
2693
4425
  console.log(chalk7.blue("\n[CHECK] Pre-Deploy Accessibility\n"));
2694
4426
  console.log(chalk7.dim("Before deploying a live website, accessibility compliance is required.\n"));
@@ -2700,7 +4432,7 @@ async function a11yPreDeploy() {
2700
4432
  const reportContent = await readFile5(reportPath, "utf-8");
2701
4433
  const report = JSON.parse(reportContent);
2702
4434
  if (report.passed) {
2703
- 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): ");
2704
4436
  if (addBadge.toLowerCase() === "y") {
2705
4437
  await a11yBadge({});
2706
4438
  }
@@ -2710,7 +4442,7 @@ async function a11yPreDeploy() {
2710
4442
  console.log(" 1) Fix issues now (recommended)");
2711
4443
  console.log(" 2) Deploy anyway (not recommended)");
2712
4444
  console.log(" 3) Cancel deployment\n");
2713
- 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): ");
2714
4446
  if (choice === "1") {
2715
4447
  await a11yFix({});
2716
4448
  await a11yCheck({});
@@ -2724,7 +4456,7 @@ async function a11yPreDeploy() {
2724
4456
  return false;
2725
4457
  }
2726
4458
  } finally {
2727
- prompt2.close();
4459
+ prompt3.close();
2728
4460
  }
2729
4461
  }
2730
4462
 
@@ -2819,7 +4551,7 @@ Guidelines:
2819
4551
  Output your response as valid JSON.`;
2820
4552
  async function generateIdentityCandidates(pageContent) {
2821
4553
  const agent = new ArchitectAgent({ temperature: 0.8 });
2822
- 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.
2823
4555
 
2824
4556
  ${pageContent}
2825
4557
 
@@ -2840,7 +4572,7 @@ Output as JSON:
2840
4572
  }`;
2841
4573
  const response = await agent.client.chat(
2842
4574
  IDENTITY_SYSTEM_PROMPT,
2843
- prompt2,
4575
+ prompt3,
2844
4576
  { temperature: 0.8, maxTokens: 2e3 }
2845
4577
  );
2846
4578
  const jsonMatch = response.content.match(/\{[\s\S]*\}/);
@@ -2852,7 +4584,7 @@ Output as JSON:
2852
4584
  }
2853
4585
  async function geoIdentity() {
2854
4586
  const cwd = process.cwd();
2855
- const prompt2 = createPrompt2();
4587
+ const prompt3 = createPrompt2();
2856
4588
  try {
2857
4589
  console.log(chalk8.blue("\n\u{1F3AF} GEO Identity Generator\n"));
2858
4590
  const { allowed, tier } = await checkStrongModelAccess();
@@ -2883,7 +4615,7 @@ async function geoIdentity() {
2883
4615
  console.log(` ${chalk8.dim(p.rationale)}
2884
4616
  `);
2885
4617
  });
2886
- 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: ');
2887
4619
  if (phraseChoice.toLowerCase() === "r") {
2888
4620
  console.log(chalk8.dim("\nRegenerating... Run the command again.\n"));
2889
4621
  return;
@@ -2900,7 +4632,7 @@ async function geoIdentity() {
2900
4632
  console.log(` ${chalk8.dim(d.rationale)}
2901
4633
  `);
2902
4634
  });
2903
- 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: ');
2904
4636
  if (descChoice.toLowerCase() === "r") {
2905
4637
  console.log(chalk8.dim("\nRegenerating... Run the command again.\n"));
2906
4638
  return;
@@ -2925,7 +4657,7 @@ async function geoIdentity() {
2925
4657
  console.log();
2926
4658
  console.log(chalk8.cyan(`Use 'archon geo schema' to generate JSON-LD.`));
2927
4659
  } finally {
2928
- prompt2.close();
4660
+ prompt3.close();
2929
4661
  }
2930
4662
  }
2931
4663
  async function geoSchema(options) {
@@ -3042,7 +4774,7 @@ async function geoFaq(options) {
3042
4774
  }
3043
4775
  console.log(chalk8.dim("Generating FAQ content with AI...\n"));
3044
4776
  const agent = new ArchitectAgent({ temperature: 0.7 });
3045
- const prompt2 = `Generate FAQ content for a product/service with:
4777
+ const prompt3 = `Generate FAQ content for a product/service with:
3046
4778
  - Brand phrase: "${identityPhrase}"
3047
4779
  - Description: "${shortDescription}"
3048
4780
 
@@ -3055,7 +4787,7 @@ Generate 6-8 FAQs as JSON:
3055
4787
  }`;
3056
4788
  const response = await agent.client.chat(
3057
4789
  FAQ_SYSTEM_PROMPT,
3058
- prompt2,
4790
+ prompt3,
3059
4791
  { temperature: 0.7, maxTokens: 2e3 }
3060
4792
  );
3061
4793
  const jsonMatch = response.content.match(/\{[\s\S]*\}/);
@@ -3417,7 +5149,7 @@ async function seoCheck(options) {
3417
5149
  Full report saved to: .archon/seo-report.json`));
3418
5150
  }
3419
5151
  async function seoFix(options) {
3420
- const prompt2 = createPrompt3();
5152
+ const prompt3 = createPrompt3();
3421
5153
  try {
3422
5154
  console.log(chalk9.blue("\n\u{1F527} SEO Auto-Fix\n"));
3423
5155
  const reportPath = join8(process.cwd(), ".archon/seo-report.json");
@@ -3473,7 +5205,7 @@ async function seoFix(options) {
3473
5205
  console.log(chalk9.green(` + ${tag.slice(0, 70)}${tag.length > 70 ? "..." : ""}`));
3474
5206
  }
3475
5207
  if (!options.dryRun) {
3476
- 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): "));
3477
5209
  if (confirm.toLowerCase() === "y") {
3478
5210
  await writeFile7(filePath, newContent);
3479
5211
  totalFixes += tagsToAdd.length;
@@ -3496,11 +5228,11 @@ ${totalFixes} fixes would be applied. Run without --dry-run to apply.`));
3496
5228
  console.log(chalk9.dim('Run "archon seo check" to verify fixes.'));
3497
5229
  }
3498
5230
  } finally {
3499
- prompt2.close();
5231
+ prompt3.close();
3500
5232
  }
3501
5233
  }
3502
5234
  async function seoOpenGraph(options) {
3503
- const prompt2 = createPrompt3();
5235
+ const prompt3 = createPrompt3();
3504
5236
  try {
3505
5237
  console.log(chalk9.blue("\n\u{1F4F1} Add Open Graph Tags\n"));
3506
5238
  let targetFile;
@@ -3519,7 +5251,7 @@ async function seoOpenGraph(options) {
3519
5251
  if (files.length > 10) {
3520
5252
  console.log(chalk9.dim(` ... and ${files.length - 10} more`));
3521
5253
  }
3522
- const fileChoice = await prompt2.ask("\nEnter file path or number: ");
5254
+ const fileChoice = await prompt3.ask("\nEnter file path or number: ");
3523
5255
  const num = parseInt(fileChoice, 10);
3524
5256
  if (num > 0 && num <= files.length) {
3525
5257
  targetFile = files[num - 1] ?? "";
@@ -3531,10 +5263,10 @@ async function seoOpenGraph(options) {
3531
5263
  console.log(chalk9.red(`File not found: ${targetFile}`));
3532
5264
  return;
3533
5265
  }
3534
- const ogTitle = await prompt2.ask("og:title (page title for social): ");
3535
- const ogDescription = await prompt2.ask("og:description (page description): ");
3536
- const ogImage = await prompt2.ask("og:image (full URL to image): ");
3537
- 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): ");
3538
5270
  const tags = [
3539
5271
  `<meta property="og:type" content="website">`,
3540
5272
  `<meta property="og:title" content="${ogTitle}">`,
@@ -3551,7 +5283,7 @@ async function seoOpenGraph(options) {
3551
5283
  }
3552
5284
  console.log(chalk9.dim("\nTags to add:"));
3553
5285
  tags.forEach((tag) => console.log(chalk9.green(` + ${tag}`)));
3554
- const confirm = await prompt2.ask("\nApply changes? (y/N): ");
5286
+ const confirm = await prompt3.ask("\nApply changes? (y/N): ");
3555
5287
  if (confirm.toLowerCase() === "y") {
3556
5288
  const newContent = content.slice(0, insertPoint.index) + "\n" + tags.map((tag) => insertPoint.indent + tag).join("\n") + content.slice(insertPoint.index);
3557
5289
  await writeFile7(targetFile, newContent);
@@ -3560,11 +5292,11 @@ async function seoOpenGraph(options) {
3560
5292
  console.log(chalk9.dim("Cancelled."));
3561
5293
  }
3562
5294
  } finally {
3563
- prompt2.close();
5295
+ prompt3.close();
3564
5296
  }
3565
5297
  }
3566
5298
  async function seoTwitter(options) {
3567
- const prompt2 = createPrompt3();
5299
+ const prompt3 = createPrompt3();
3568
5300
  try {
3569
5301
  console.log(chalk9.blue("\n\u{1F426} Add Twitter Card Tags\n"));
3570
5302
  let targetFile;
@@ -3583,7 +5315,7 @@ async function seoTwitter(options) {
3583
5315
  if (files.length > 10) {
3584
5316
  console.log(chalk9.dim(` ... and ${files.length - 10} more`));
3585
5317
  }
3586
- const fileChoice = await prompt2.ask("\nEnter file path or number: ");
5318
+ const fileChoice = await prompt3.ask("\nEnter file path or number: ");
3587
5319
  const num = parseInt(fileChoice, 10);
3588
5320
  if (num > 0 && num <= files.length) {
3589
5321
  targetFile = files[num - 1] ?? "";
@@ -3596,10 +5328,10 @@ async function seoTwitter(options) {
3596
5328
  return;
3597
5329
  }
3598
5330
  console.log(chalk9.dim("Card types: summary, summary_large_image, app, player"));
3599
- const cardType = await prompt2.ask("twitter:card type (default: summary_large_image): ") || "summary_large_image";
3600
- const twitterTitle = await prompt2.ask("twitter:title: ");
3601
- const twitterDescription = await prompt2.ask("twitter:description: ");
3602
- 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): ");
3603
5335
  const tags = [
3604
5336
  `<meta name="twitter:card" content="${cardType}">`,
3605
5337
  `<meta name="twitter:title" content="${twitterTitle}">`,
@@ -3615,7 +5347,7 @@ async function seoTwitter(options) {
3615
5347
  }
3616
5348
  console.log(chalk9.dim("\nTags to add:"));
3617
5349
  tags.forEach((tag) => console.log(chalk9.green(` + ${tag}`)));
3618
- const confirm = await prompt2.ask("\nApply changes? (y/N): ");
5350
+ const confirm = await prompt3.ask("\nApply changes? (y/N): ");
3619
5351
  if (confirm.toLowerCase() === "y") {
3620
5352
  const newContent = content.slice(0, insertPoint.index) + "\n" + tags.map((tag) => insertPoint.indent + tag).join("\n") + content.slice(insertPoint.index);
3621
5353
  await writeFile7(targetFile, newContent);
@@ -3624,7 +5356,7 @@ async function seoTwitter(options) {
3624
5356
  console.log(chalk9.dim("Cancelled."));
3625
5357
  }
3626
5358
  } finally {
3627
- prompt2.close();
5359
+ prompt3.close();
3628
5360
  }
3629
5361
  }
3630
5362
  function createSeoCommand() {
@@ -4175,7 +5907,7 @@ import { createClient as createClient3 } from "@supabase/supabase-js";
4175
5907
  import { readFile as readFile10 } from "fs/promises";
4176
5908
  import { existsSync as existsSync13 } from "fs";
4177
5909
  import { join as join12, extname as extname2 } from "path";
4178
- import { createHash } from "crypto";
5910
+ import { createHash as createHash2 } from "crypto";
4179
5911
  var CHUNK_SIZE2 = 1e3;
4180
5912
  var CHUNK_OVERLAP2 = 200;
4181
5913
  var INDEXABLE_EXTENSIONS2 = /* @__PURE__ */ new Set([
@@ -4289,7 +6021,7 @@ var CloudIndexer = class {
4289
6021
  * Compute file hash for change detection
4290
6022
  */
4291
6023
  async computeFileHash(content) {
4292
- return createHash("sha256").update(content).digest("hex").slice(0, 16);
6024
+ return createHash2("sha256").update(content).digest("hex").slice(0, 16);
4293
6025
  }
4294
6026
  /**
4295
6027
  * Index a single file
@@ -4748,13 +6480,422 @@ async function githubDisconnect() {
4748
6480
  }
4749
6481
  }
4750
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
+
4751
6892
  // src/cli/index.ts
4752
6893
  var program = new Command4();
4753
6894
  program.name("archon").description("Local-first AI-powered development governance").version("1.1.0").action(async () => {
4754
6895
  const cwd = process.cwd();
4755
6896
  const wasInitialized = isInitialized(cwd);
4756
6897
  if (!wasInitialized) {
4757
- 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"));
4758
6899
  await init({ analyze: true, git: true });
4759
6900
  }
4760
6901
  await start({ skipGovernanceBanner: !wasInitialized });
@@ -4762,7 +6903,7 @@ program.name("archon").description("Local-first AI-powered development governanc
4762
6903
  program.command("login").description("Authenticate with ArchonDev").option("-p, --provider <provider>", "OAuth provider (github or google)", "github").action(async (options) => {
4763
6904
  const provider = options.provider;
4764
6905
  if (provider !== "github" && provider !== "google") {
4765
- console.error(chalk14.red('Invalid provider. Use "github" or "google"'));
6906
+ console.error(chalk15.red('Invalid provider. Use "github" or "google"'));
4766
6907
  process.exit(1);
4767
6908
  }
4768
6909
  await login(provider);
@@ -4773,6 +6914,10 @@ program.command("logout").description("Clear stored authentication").action(asyn
4773
6914
  program.command("status").description("Show current user and project status").action(async () => {
4774
6915
  await status();
4775
6916
  });
6917
+ program.command("pricing").description("View and switch pricing tiers (Free, BYOK, Credits)").action(async () => {
6918
+ const { showTierSwitchMenu } = await import("./tier-selection-JYMYBIRV.js");
6919
+ await showTierSwitchMenu();
6920
+ });
4776
6921
  program.command("init").description("Initialize ArchonDev in current project").option("--analyze", "Run enhanced analysis of codebase").option("--no-git", "Skip git initialization").action(async (options) => {
4777
6922
  await init(options);
4778
6923
  });
@@ -4816,7 +6961,7 @@ var creditsCommand = program.command("credits").description("Manage credit balan
4816
6961
  creditsCommand.command("show").description("Show current credit balance").action(async () => {
4817
6962
  await showCredits();
4818
6963
  });
4819
- creditsCommand.command("add").description("Add credits via Stripe checkout").option("-a, --amount <dollars>", "Amount in dollars (min $5)", "10").action(async (options) => {
6964
+ creditsCommand.command("add").description("Add credits via Stripe checkout").option("-a, --amount <dollars>", "Amount in dollars (min $5)", "5").action(async (options) => {
4820
6965
  await addCredits(options);
4821
6966
  });
4822
6967
  creditsCommand.command("history").description("Show recent token usage").option("-l, --limit <count>", "Number of records to show", "20").action(async (options) => {
@@ -4943,10 +7088,29 @@ cleanupCmd.command("check").description("Analyze workspace for bloat and mainten
4943
7088
  cleanupCmd.command("run").description("Execute cleanup (archive old entries, remove stale files)").action(cleanupRun);
4944
7089
  cleanupCmd.command("auto").description("Enable/disable automatic cleanup checks").argument("[action]", "enable, disable, or status", "status").action(async (action) => {
4945
7090
  if (action !== "enable" && action !== "disable" && action !== "status") {
4946
- console.error(chalk14.red("Invalid action. Use: enable, disable, or status"));
7091
+ console.error(chalk15.red("Invalid action. Use: enable, disable, or status"));
4947
7092
  process.exit(1);
4948
7093
  }
4949
7094
  await cleanupAuto(action);
4950
7095
  });
4951
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
+ });
4952
7116
  program.parse();