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