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