@ryuenn3123/agentic-senior-core 2.5.8 → 2.5.10
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/.agent-context/review-checklists/frontend-excellence-rubric.md +9 -0
- package/.agent-context/rules/frontend-architecture.md +14 -0
- package/.agent-context/state/memory-continuity-benchmark.json +1 -1
- package/.cursorrules +1 -1
- package/.windsurfrules +1 -1
- package/README.md +6 -0
- package/lib/cli/architect.mjs +493 -0
- package/lib/cli/commands/init.mjs +205 -63
- package/lib/cli/compiler.mjs +2 -0
- package/lib/cli/utils.mjs +4 -1
- package/package.json +1 -1
- package/scripts/frontend-usability-audit.mjs +17 -0
- package/scripts/release-gate.mjs +3 -0
|
@@ -43,6 +43,15 @@ Release recommendation:
|
|
|
43
43
|
- [ ] Error, empty, and loading states provide clear next actions.
|
|
44
44
|
- [ ] User journey avoids dead ends and hidden critical actions.
|
|
45
45
|
|
|
46
|
+
## 7. Template Diversity and Originality
|
|
47
|
+
- [ ] Output is not a copy of a generic starter template or repeated AI layout pattern.
|
|
48
|
+
- [ ] Layout composition shows intentional variation in structure and hierarchy.
|
|
49
|
+
- [ ] Visual intent, interaction quality, and conversion clarity are all explicitly reviewed together.
|
|
50
|
+
|
|
51
|
+
## Low-Diversity Template Output Policy
|
|
52
|
+
- If output is judged as low-diversity template output, release is blocked until layout direction is revised.
|
|
53
|
+
- Reviewer must record the failing dimensions and expected redesign direction before re-run.
|
|
54
|
+
|
|
46
55
|
## Benchmark Expectation
|
|
47
56
|
- Treat MiniMax frontend references as baseline, not target ceiling.
|
|
48
57
|
- Target visual and interaction quality aligned with top award-grade manual design workflows (Awwwards-level reference quality).
|
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
> A complex UI is built from simple, mathematically robust functions. State is dangerous; isolate it.
|
|
4
4
|
|
|
5
|
+
## 0. Frontend Designer Mode (Auto Activation)
|
|
6
|
+
When the user request is UI-facing, frontend design governance activates automatically. No manual mode toggle is required.
|
|
7
|
+
|
|
8
|
+
UI scope trigger signals (any one is enough):
|
|
9
|
+
- keywords such as: ui, ux, page, screen, component, layout, landing, dashboard, form, onboarding, animation, interaction
|
|
10
|
+
- explicit requests to improve visual quality, conversion clarity, or interaction behavior
|
|
11
|
+
- feature requests that include frontend deliverables even when backend changes are also included
|
|
12
|
+
|
|
13
|
+
Mandatory behavior when triggered:
|
|
14
|
+
- apply frontend excellence rubric checks from `.agent-context/review-checklists/frontend-excellence-rubric.md`
|
|
15
|
+
- apply usability checks from `.agent-context/review-checklists/frontend-usability.md`
|
|
16
|
+
- score and review generated UI work against visual intent, interaction quality, and conversion clarity
|
|
17
|
+
- reject template-only repetitive outputs and force a distinct layout direction
|
|
18
|
+
|
|
5
19
|
## 1. File Structure (Feature-Driven Design)
|
|
6
20
|
Organize your application by feature domain, not by file type.
|
|
7
21
|
- **BANNED:** Monolithic directories like `/components` (with 500 files), `/hooks`, `/api`.
|
package/.cursorrules
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AGENTIC-SENIOR-CORE DYNAMIC GOVERNANCE RULESET
|
|
2
2
|
|
|
3
|
-
Generated by Agentic-Senior-Core CLI v2.5.
|
|
3
|
+
Generated by Agentic-Senior-Core CLI v2.5.10
|
|
4
4
|
Timestamp: 2026-04-15T00:14:51.184Z
|
|
5
5
|
Selected profile: beginner
|
|
6
6
|
Selected policy file: .agent-context/policies/llm-judge-threshold.json
|
package/.windsurfrules
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AGENTIC-SENIOR-CORE DYNAMIC GOVERNANCE RULESET
|
|
2
2
|
|
|
3
|
-
Generated by Agentic-Senior-Core CLI v2.5.
|
|
3
|
+
Generated by Agentic-Senior-Core CLI v2.5.10
|
|
4
4
|
Timestamp: 2026-04-15T00:14:51.184Z
|
|
5
5
|
Selected profile: beginner
|
|
6
6
|
Selected policy file: .agent-context/policies/llm-judge-threshold.json
|
package/README.md
CHANGED
|
@@ -30,6 +30,12 @@ Optional team default path:
|
|
|
30
30
|
npx @ryuenn3123/agentic-senior-core init --profile-pack startup
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
Project-description-first path (AI as Architect with veto control):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx @ryuenn3123/agentic-senior-core init --project-description "Machine learning API for fraud detection"
|
|
37
|
+
```
|
|
38
|
+
|
|
33
39
|
---
|
|
34
40
|
|
|
35
41
|
## Before / After
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { BLUEPRINT_RECOMMENDATIONS } from './constants.mjs';
|
|
6
|
+
import { ensureDirectory, pathExists, toTitleCase } from './utils.mjs';
|
|
7
|
+
|
|
8
|
+
export const ARCHITECT_DEFAULT_TOKEN_BUDGET = 900;
|
|
9
|
+
export const ARCHITECT_DEFAULT_TIMEOUT_MS = 1500;
|
|
10
|
+
export const ARCHITECT_MIN_TOKEN_BUDGET = 200;
|
|
11
|
+
export const ARCHITECT_MAX_TOKEN_BUDGET = 4000;
|
|
12
|
+
export const ARCHITECT_MIN_TIMEOUT_MS = 200;
|
|
13
|
+
export const ARCHITECT_MAX_TIMEOUT_MS = 10000;
|
|
14
|
+
|
|
15
|
+
const ARCHITECT_PREFERENCE_FILE_PATH = process.env.AGENTIC_ARCHITECT_PREF_FILE
|
|
16
|
+
? path.resolve(process.env.AGENTIC_ARCHITECT_PREF_FILE)
|
|
17
|
+
: path.join(os.homedir(), '.agentic-senior-core', 'architect-preferences.json');
|
|
18
|
+
|
|
19
|
+
const STACK_SIGNAL_WEIGHTS = {
|
|
20
|
+
'typescript.md': [
|
|
21
|
+
{ term: 'typescript', weight: 0.9 },
|
|
22
|
+
{ term: 'javascript', weight: 0.8 },
|
|
23
|
+
{ term: 'node', weight: 0.55 },
|
|
24
|
+
{ term: 'next.js', weight: 0.7 },
|
|
25
|
+
{ term: 'nextjs', weight: 0.7 },
|
|
26
|
+
{ term: 'react', weight: 0.45 },
|
|
27
|
+
{ term: 'web app', weight: 0.45 },
|
|
28
|
+
{ term: 'frontend', weight: 0.4 },
|
|
29
|
+
{ term: 'dashboard', weight: 0.35 },
|
|
30
|
+
],
|
|
31
|
+
'python.md': [
|
|
32
|
+
{ term: 'python', weight: 0.95 },
|
|
33
|
+
{ term: 'fastapi', weight: 0.8 },
|
|
34
|
+
{ term: 'machine learning', weight: 0.8 },
|
|
35
|
+
{ term: 'ml', weight: 0.4 },
|
|
36
|
+
{ term: 'data', weight: 0.45 },
|
|
37
|
+
{ term: 'ai', weight: 0.45 },
|
|
38
|
+
{ term: 'automation', weight: 0.35 },
|
|
39
|
+
{ term: 'analytics', weight: 0.35 },
|
|
40
|
+
],
|
|
41
|
+
'java.md': [
|
|
42
|
+
{ term: 'java', weight: 0.9 },
|
|
43
|
+
{ term: 'spring', weight: 0.75 },
|
|
44
|
+
{ term: 'enterprise', weight: 0.45 },
|
|
45
|
+
{ term: 'bank', weight: 0.35 },
|
|
46
|
+
{ term: 'regulated', weight: 0.35 },
|
|
47
|
+
{ term: 'jvm', weight: 0.35 },
|
|
48
|
+
],
|
|
49
|
+
'php.md': [
|
|
50
|
+
{ term: 'php', weight: 0.9 },
|
|
51
|
+
{ term: 'laravel', weight: 0.8 },
|
|
52
|
+
{ term: 'cms', weight: 0.4 },
|
|
53
|
+
{ term: 'wordpress', weight: 0.35 },
|
|
54
|
+
],
|
|
55
|
+
'go.md': [
|
|
56
|
+
{ term: 'go', weight: 0.5 },
|
|
57
|
+
{ term: 'golang', weight: 0.9 },
|
|
58
|
+
{ term: 'high throughput', weight: 0.55 },
|
|
59
|
+
{ term: 'microservice', weight: 0.45 },
|
|
60
|
+
{ term: 'kubernetes', weight: 0.45 },
|
|
61
|
+
{ term: 'latency', weight: 0.35 },
|
|
62
|
+
{ term: 'concurrency', weight: 0.35 },
|
|
63
|
+
],
|
|
64
|
+
'csharp.md': [
|
|
65
|
+
{ term: '.net', weight: 0.9 },
|
|
66
|
+
{ term: 'dotnet', weight: 0.9 },
|
|
67
|
+
{ term: 'c#', weight: 0.75 },
|
|
68
|
+
{ term: 'asp.net', weight: 0.8 },
|
|
69
|
+
{ term: 'microsoft', weight: 0.35 },
|
|
70
|
+
],
|
|
71
|
+
'rust.md': [
|
|
72
|
+
{ term: 'rust', weight: 0.9 },
|
|
73
|
+
{ term: 'systems', weight: 0.45 },
|
|
74
|
+
{ term: 'performance', weight: 0.35 },
|
|
75
|
+
{ term: 'memory safety', weight: 0.4 },
|
|
76
|
+
{ term: 'low latency', weight: 0.4 },
|
|
77
|
+
],
|
|
78
|
+
'ruby.md': [
|
|
79
|
+
{ term: 'ruby', weight: 0.9 },
|
|
80
|
+
{ term: 'rails', weight: 0.8 },
|
|
81
|
+
{ term: 'monolith', weight: 0.35 },
|
|
82
|
+
{ term: 'crud', weight: 0.3 },
|
|
83
|
+
],
|
|
84
|
+
'react-native.md': [
|
|
85
|
+
{ term: 'react native', weight: 0.9 },
|
|
86
|
+
{ term: 'mobile', weight: 0.4 },
|
|
87
|
+
{ term: 'android', weight: 0.35 },
|
|
88
|
+
{ term: 'ios', weight: 0.35 },
|
|
89
|
+
{ term: 'cross-platform', weight: 0.4 },
|
|
90
|
+
],
|
|
91
|
+
'flutter.md': [
|
|
92
|
+
{ term: 'flutter', weight: 0.95 },
|
|
93
|
+
{ term: 'dart', weight: 0.8 },
|
|
94
|
+
{ term: 'mobile', weight: 0.4 },
|
|
95
|
+
{ term: 'android', weight: 0.35 },
|
|
96
|
+
{ term: 'ios', weight: 0.35 },
|
|
97
|
+
{ term: 'cross-platform', weight: 0.4 },
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const STACK_TRADEOFF_SUMMARIES = {
|
|
102
|
+
'typescript.md': 'great fullstack velocity, but dependency churn and runtime startup need discipline.',
|
|
103
|
+
'python.md': 'fast AI and API delivery, but strict latency targets may need extra optimization.',
|
|
104
|
+
'java.md': 'strong enterprise reliability, but setup and boilerplate are heavier.',
|
|
105
|
+
'php.md': 'rapid web product iteration, but architecture boundaries must be guarded early.',
|
|
106
|
+
'go.md': 'excellent throughput and operational simplicity, but abstractions are intentionally minimal.',
|
|
107
|
+
'csharp.md': 'strong for Microsoft-centered teams, but cross-platform tooling choices should be explicit.',
|
|
108
|
+
'rust.md': 'top performance and safety, but development ramp-up is steeper.',
|
|
109
|
+
'ruby.md': 'high productivity for product teams, but scaling strategy should be planned early.',
|
|
110
|
+
'react-native.md': 'single codebase for mobile, but native edge cases still require platform attention.',
|
|
111
|
+
'flutter.md': 'consistent UI across mobile platforms, but ecosystem fit should be checked per package.',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
function clampNumericValue(value, minimumValue, maximumValue) {
|
|
115
|
+
return Math.min(Math.max(value, minimumValue), maximumValue);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function estimateTokenUsage(textValue) {
|
|
119
|
+
return Math.ceil(String(textValue || '').length / 4);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function resolveConfidenceLabel(confidenceScore) {
|
|
123
|
+
if (confidenceScore >= 0.85) {
|
|
124
|
+
return 'high';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (confidenceScore >= 0.7) {
|
|
128
|
+
return 'medium';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return 'low';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function resolveRecommendedBlueprintFileName(stackFileName, blueprintFileNames) {
|
|
135
|
+
const recommendedBlueprintFileName = BLUEPRINT_RECOMMENDATIONS[stackFileName] || null;
|
|
136
|
+
if (recommendedBlueprintFileName && blueprintFileNames.includes(recommendedBlueprintFileName)) {
|
|
137
|
+
return recommendedBlueprintFileName;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return blueprintFileNames[0] || null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function buildFallbackRecommendation({
|
|
144
|
+
stackFileNames,
|
|
145
|
+
blueprintFileNames,
|
|
146
|
+
tokenBudget,
|
|
147
|
+
timeoutMs,
|
|
148
|
+
usedTokens,
|
|
149
|
+
elapsedMs,
|
|
150
|
+
timeoutTriggered,
|
|
151
|
+
}) {
|
|
152
|
+
const fallbackStackFileName = stackFileNames.includes('typescript.md')
|
|
153
|
+
? 'typescript.md'
|
|
154
|
+
: stackFileNames[0] || 'typescript.md';
|
|
155
|
+
const fallbackBlueprintFileName = resolveRecommendedBlueprintFileName(fallbackStackFileName, blueprintFileNames);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
projectDescription: '',
|
|
159
|
+
recommendedStackFileName: fallbackStackFileName,
|
|
160
|
+
recommendedBlueprintFileName: fallbackBlueprintFileName,
|
|
161
|
+
confidenceLabel: 'low',
|
|
162
|
+
confidenceScore: 0.45,
|
|
163
|
+
rationaleSentences: [
|
|
164
|
+
`I recommend ${toTitleCase(fallbackStackFileName)} with ${toTitleCase(fallbackBlueprintFileName)} as a safe fallback path.`,
|
|
165
|
+
'The architecture recommendation budget was constrained before a stronger stack signal could be computed.',
|
|
166
|
+
`Main trade-off: ${STACK_TRADEOFF_SUMMARIES[fallbackStackFileName] || 'validate ecosystem fit before production rollout.'}`,
|
|
167
|
+
],
|
|
168
|
+
alternatives: [],
|
|
169
|
+
uncertaintyNotes: [
|
|
170
|
+
timeoutTriggered
|
|
171
|
+
? 'Timeout guardrail triggered before recommendation analysis completed.'
|
|
172
|
+
: 'Input signals were not strong enough for a high-confidence architecture recommendation.',
|
|
173
|
+
],
|
|
174
|
+
signalSummary: 'fallback mode',
|
|
175
|
+
failureModes: {
|
|
176
|
+
lowConfidence: true,
|
|
177
|
+
dataConflict: false,
|
|
178
|
+
repeatedOverride: false,
|
|
179
|
+
},
|
|
180
|
+
researchBudget: {
|
|
181
|
+
tokenBudget,
|
|
182
|
+
timeoutMs,
|
|
183
|
+
usedTokens: Math.min(usedTokens, tokenBudget),
|
|
184
|
+
elapsedMs: Math.min(elapsedMs, timeoutMs),
|
|
185
|
+
tokenBudgetCapped: usedTokens >= tokenBudget,
|
|
186
|
+
timeoutTriggered,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function recommendArchitecture({
|
|
192
|
+
projectDescription,
|
|
193
|
+
projectDetection,
|
|
194
|
+
stackFileNames,
|
|
195
|
+
blueprintFileNames,
|
|
196
|
+
tokenBudget = ARCHITECT_DEFAULT_TOKEN_BUDGET,
|
|
197
|
+
timeoutMs = ARCHITECT_DEFAULT_TIMEOUT_MS,
|
|
198
|
+
}) {
|
|
199
|
+
const startedAt = Date.now();
|
|
200
|
+
const boundedTokenBudget = clampNumericValue(tokenBudget, ARCHITECT_MIN_TOKEN_BUDGET, ARCHITECT_MAX_TOKEN_BUDGET);
|
|
201
|
+
const boundedTimeoutMs = clampNumericValue(timeoutMs, ARCHITECT_MIN_TIMEOUT_MS, ARCHITECT_MAX_TIMEOUT_MS);
|
|
202
|
+
const normalizedDescription = String(projectDescription || '').trim().toLowerCase();
|
|
203
|
+
const effectiveDescriptionSeed = normalizedDescription || 'general software project';
|
|
204
|
+
let effectiveDescription = effectiveDescriptionSeed;
|
|
205
|
+
let usedTokens = estimateTokenUsage(effectiveDescription) + 120;
|
|
206
|
+
const uncertaintyNotes = [];
|
|
207
|
+
|
|
208
|
+
if (usedTokens > boundedTokenBudget) {
|
|
209
|
+
effectiveDescription = effectiveDescription.slice(0, Math.max(120, boundedTokenBudget * 4));
|
|
210
|
+
usedTokens = boundedTokenBudget;
|
|
211
|
+
uncertaintyNotes.push('Token budget guardrail trimmed input context before recommendation.');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if ((Date.now() - startedAt) > boundedTimeoutMs) {
|
|
215
|
+
return buildFallbackRecommendation({
|
|
216
|
+
stackFileNames,
|
|
217
|
+
blueprintFileNames,
|
|
218
|
+
tokenBudget: boundedTokenBudget,
|
|
219
|
+
timeoutMs: boundedTimeoutMs,
|
|
220
|
+
usedTokens,
|
|
221
|
+
elapsedMs: Date.now() - startedAt,
|
|
222
|
+
timeoutTriggered: true,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const detectionScoreByStackFileName = new Map();
|
|
227
|
+
for (const rankedCandidate of projectDetection?.rankedCandidates || []) {
|
|
228
|
+
const confidenceScore = Number(rankedCandidate.confidenceScore) || 0;
|
|
229
|
+
if (confidenceScore <= 0) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
detectionScoreByStackFileName.set(
|
|
234
|
+
rankedCandidate.stackFileName,
|
|
235
|
+
confidenceScore * 1.75
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const scoredStackCandidates = stackFileNames.map((stackFileName) => {
|
|
240
|
+
const configuredSignals = STACK_SIGNAL_WEIGHTS[stackFileName] || [];
|
|
241
|
+
const matchedSignals = [];
|
|
242
|
+
let keywordSignalScore = 0;
|
|
243
|
+
|
|
244
|
+
for (const configuredSignal of configuredSignals) {
|
|
245
|
+
if (!effectiveDescription.includes(configuredSignal.term)) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
keywordSignalScore += configuredSignal.weight;
|
|
250
|
+
matchedSignals.push(configuredSignal.term);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const detectionSignalScore = detectionScoreByStackFileName.get(stackFileName) || 0;
|
|
254
|
+
const totalScore = 0.2 + keywordSignalScore + detectionSignalScore;
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
stackFileName,
|
|
258
|
+
totalScore,
|
|
259
|
+
keywordSignalScore,
|
|
260
|
+
detectionSignalScore,
|
|
261
|
+
matchedSignals,
|
|
262
|
+
};
|
|
263
|
+
}).sort((leftCandidate, rightCandidate) => rightCandidate.totalScore - leftCandidate.totalScore);
|
|
264
|
+
|
|
265
|
+
if (scoredStackCandidates.length === 0) {
|
|
266
|
+
return buildFallbackRecommendation({
|
|
267
|
+
stackFileNames,
|
|
268
|
+
blueprintFileNames,
|
|
269
|
+
tokenBudget: boundedTokenBudget,
|
|
270
|
+
timeoutMs: boundedTimeoutMs,
|
|
271
|
+
usedTokens,
|
|
272
|
+
elapsedMs: Date.now() - startedAt,
|
|
273
|
+
timeoutTriggered: false,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const strongestCandidate = scoredStackCandidates[0];
|
|
278
|
+
const secondCandidate = scoredStackCandidates[1] || null;
|
|
279
|
+
const scoreGap = secondCandidate
|
|
280
|
+
? strongestCandidate.totalScore - secondCandidate.totalScore
|
|
281
|
+
: strongestCandidate.totalScore;
|
|
282
|
+
|
|
283
|
+
let confidenceScore = 0.55
|
|
284
|
+
+ Math.min(strongestCandidate.totalScore / 8, 0.25)
|
|
285
|
+
+ Math.min(Math.max(scoreGap, 0) / 3, 0.18);
|
|
286
|
+
|
|
287
|
+
if (strongestCandidate.matchedSignals.length === 0) {
|
|
288
|
+
confidenceScore -= 0.2;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (secondCandidate && scoreGap < 0.18) {
|
|
292
|
+
confidenceScore -= 0.1;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
confidenceScore = clampNumericValue(confidenceScore, 0.35, 0.97);
|
|
296
|
+
|
|
297
|
+
const confidenceLabel = resolveConfidenceLabel(confidenceScore);
|
|
298
|
+
const lowConfidence = confidenceScore < 0.7;
|
|
299
|
+
const dataConflict = Boolean(secondCandidate && scoreGap < 0.18);
|
|
300
|
+
|
|
301
|
+
if (lowConfidence) {
|
|
302
|
+
uncertaintyNotes.push('Low confidence: description did not map strongly to a single stack profile.');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (dataConflict) {
|
|
306
|
+
uncertaintyNotes.push('Data conflict: top stack candidates are close, so trade-offs need manual confirmation.');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const recommendedStackFileName = strongestCandidate.stackFileName;
|
|
310
|
+
const recommendedBlueprintFileName = resolveRecommendedBlueprintFileName(recommendedStackFileName, blueprintFileNames);
|
|
311
|
+
const signalSummary = strongestCandidate.matchedSignals.length > 0
|
|
312
|
+
? strongestCandidate.matchedSignals.slice(0, 4).join(', ')
|
|
313
|
+
: 'limited direct stack keywords';
|
|
314
|
+
|
|
315
|
+
const rationaleSentences = [
|
|
316
|
+
`I recommend ${toTitleCase(recommendedStackFileName)} with ${toTitleCase(recommendedBlueprintFileName)} for this project.`,
|
|
317
|
+
`The strongest evidence in your description is ${signalSummary}, combined with available repository signals.`,
|
|
318
|
+
`Main trade-off: ${STACK_TRADEOFF_SUMMARIES[recommendedStackFileName] || 'validate ecosystem fit before production rollout.'}`,
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
if (lowConfidence) {
|
|
322
|
+
rationaleSentences.push('Confidence is low, so review alternatives before finalizing architecture.');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const alternatives = scoredStackCandidates
|
|
326
|
+
.slice(1, 3)
|
|
327
|
+
.map((stackCandidate) => {
|
|
328
|
+
const alternativeBlueprintFileName = resolveRecommendedBlueprintFileName(stackCandidate.stackFileName, blueprintFileNames);
|
|
329
|
+
return {
|
|
330
|
+
stackFileName: stackCandidate.stackFileName,
|
|
331
|
+
blueprintFileName: alternativeBlueprintFileName,
|
|
332
|
+
oneLineTradeoff: STACK_TRADEOFF_SUMMARIES[stackCandidate.stackFileName]
|
|
333
|
+
|| 'validate fit with your runtime and team constraints.',
|
|
334
|
+
};
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
const elapsedMs = Date.now() - startedAt;
|
|
338
|
+
if (elapsedMs > boundedTimeoutMs) {
|
|
339
|
+
return buildFallbackRecommendation({
|
|
340
|
+
stackFileNames,
|
|
341
|
+
blueprintFileNames,
|
|
342
|
+
tokenBudget: boundedTokenBudget,
|
|
343
|
+
timeoutMs: boundedTimeoutMs,
|
|
344
|
+
usedTokens,
|
|
345
|
+
elapsedMs,
|
|
346
|
+
timeoutTriggered: true,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
projectDescription: String(projectDescription || '').trim(),
|
|
352
|
+
recommendedStackFileName,
|
|
353
|
+
recommendedBlueprintFileName,
|
|
354
|
+
confidenceLabel,
|
|
355
|
+
confidenceScore: Number(confidenceScore.toFixed(2)),
|
|
356
|
+
rationaleSentences,
|
|
357
|
+
alternatives,
|
|
358
|
+
uncertaintyNotes,
|
|
359
|
+
signalSummary,
|
|
360
|
+
failureModes: {
|
|
361
|
+
lowConfidence,
|
|
362
|
+
dataConflict,
|
|
363
|
+
repeatedOverride: false,
|
|
364
|
+
},
|
|
365
|
+
researchBudget: {
|
|
366
|
+
tokenBudget: boundedTokenBudget,
|
|
367
|
+
timeoutMs: boundedTimeoutMs,
|
|
368
|
+
usedTokens: Math.min(usedTokens, boundedTokenBudget),
|
|
369
|
+
elapsedMs,
|
|
370
|
+
tokenBudgetCapped: usedTokens >= boundedTokenBudget,
|
|
371
|
+
timeoutTriggered: false,
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function formatArchitectureRecommendation(architectureRecommendation) {
|
|
377
|
+
const outputLines = [
|
|
378
|
+
'\nArchitecture recommendation (project-description-first):',
|
|
379
|
+
`- Stack: ${toTitleCase(architectureRecommendation.recommendedStackFileName)}`,
|
|
380
|
+
`- Blueprint: ${toTitleCase(architectureRecommendation.recommendedBlueprintFileName)}`,
|
|
381
|
+
`- Confidence: ${architectureRecommendation.confidenceLabel} (${architectureRecommendation.confidenceScore})`,
|
|
382
|
+
'- Rationale:',
|
|
383
|
+
...architectureRecommendation.rationaleSentences.map((sentence, sentenceIndex) => ` ${sentenceIndex + 1}. ${sentence}`),
|
|
384
|
+
'- Alternatives:',
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
if (architectureRecommendation.alternatives.length === 0) {
|
|
388
|
+
outputLines.push(' 1. No strong alternatives detected from current input signals.');
|
|
389
|
+
} else {
|
|
390
|
+
outputLines.push(
|
|
391
|
+
...architectureRecommendation.alternatives.map(
|
|
392
|
+
(alternative, alternativeIndex) => ` ${alternativeIndex + 1}. ${toTitleCase(alternative.stackFileName)} + ${toTitleCase(alternative.blueprintFileName)}: ${alternative.oneLineTradeoff}`
|
|
393
|
+
)
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (architectureRecommendation.uncertaintyNotes.length > 0) {
|
|
398
|
+
outputLines.push('- Uncertainty notes:');
|
|
399
|
+
outputLines.push(
|
|
400
|
+
...architectureRecommendation.uncertaintyNotes.map(
|
|
401
|
+
(uncertaintyNote, uncertaintyIndex) => ` ${uncertaintyIndex + 1}. ${uncertaintyNote}`
|
|
402
|
+
)
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const cautionLabels = [];
|
|
407
|
+
if (architectureRecommendation.failureModes.lowConfidence) {
|
|
408
|
+
cautionLabels.push('low-confidence');
|
|
409
|
+
}
|
|
410
|
+
if (architectureRecommendation.failureModes.dataConflict) {
|
|
411
|
+
cautionLabels.push('data-conflict');
|
|
412
|
+
}
|
|
413
|
+
if (architectureRecommendation.failureModes.repeatedOverride) {
|
|
414
|
+
cautionLabels.push('repeated-override');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (cautionLabels.length > 0) {
|
|
418
|
+
outputLines.push(`- Caution labels: ${cautionLabels.join(', ')}`);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
outputLines.push(
|
|
422
|
+
`- Research guardrails: ${architectureRecommendation.researchBudget.usedTokens}/${architectureRecommendation.researchBudget.tokenBudget} tokens, ${architectureRecommendation.researchBudget.elapsedMs}ms/${architectureRecommendation.researchBudget.timeoutMs}ms`
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
return outputLines.join('\n');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export async function readArchitectPreferenceState() {
|
|
429
|
+
if (!(await pathExists(ARCHITECT_PREFERENCE_FILE_PATH))) {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
const rawPreferenceContent = await fs.readFile(ARCHITECT_PREFERENCE_FILE_PATH, 'utf8');
|
|
435
|
+
const parsedPreferencePayload = JSON.parse(rawPreferenceContent);
|
|
436
|
+
const parsedPreferenceState = parsedPreferencePayload?.preference;
|
|
437
|
+
|
|
438
|
+
if (!parsedPreferenceState?.preferredStackFileName) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
preferredStackFileName: parsedPreferenceState.preferredStackFileName,
|
|
444
|
+
preferredBlueprintFileName: parsedPreferenceState.preferredBlueprintFileName || null,
|
|
445
|
+
overrideCount: Number(parsedPreferenceState.overrideCount) || 0,
|
|
446
|
+
lastOverrideAt: parsedPreferenceState.lastOverrideAt || null,
|
|
447
|
+
};
|
|
448
|
+
} catch {
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function createUpdatedArchitectPreference(currentPreferenceState, {
|
|
454
|
+
selectedStackFileName,
|
|
455
|
+
selectedBlueprintFileName,
|
|
456
|
+
}) {
|
|
457
|
+
const currentOverrideCount = Number(currentPreferenceState?.overrideCount) || 0;
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
preferredStackFileName: selectedStackFileName,
|
|
461
|
+
preferredBlueprintFileName: selectedBlueprintFileName,
|
|
462
|
+
overrideCount: currentOverrideCount + 1,
|
|
463
|
+
lastOverrideAt: new Date().toISOString(),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export function shouldApplyRepeatedOverridePreference(preferenceState, recommendedStackFileName) {
|
|
468
|
+
if (!preferenceState?.preferredStackFileName) {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if ((Number(preferenceState.overrideCount) || 0) < 2) {
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return preferenceState.preferredStackFileName !== recommendedStackFileName;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export async function writeArchitectPreferenceState(preferenceState) {
|
|
480
|
+
const preferencePayload = {
|
|
481
|
+
schemaVersion: '1.0.0',
|
|
482
|
+
updatedAt: new Date().toISOString(),
|
|
483
|
+
preference: {
|
|
484
|
+
preferredStackFileName: preferenceState.preferredStackFileName,
|
|
485
|
+
preferredBlueprintFileName: preferenceState.preferredBlueprintFileName,
|
|
486
|
+
overrideCount: preferenceState.overrideCount,
|
|
487
|
+
lastOverrideAt: preferenceState.lastOverrideAt,
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
await ensureDirectory(path.dirname(ARCHITECT_PREFERENCE_FILE_PATH));
|
|
492
|
+
await fs.writeFile(ARCHITECT_PREFERENCE_FILE_PATH, JSON.stringify(preferencePayload, null, 2) + '\n', 'utf8');
|
|
493
|
+
}
|
|
@@ -62,6 +62,20 @@ import {
|
|
|
62
62
|
writeMemoryContinuityState,
|
|
63
63
|
} from '../memory-continuity.mjs';
|
|
64
64
|
import { evaluateSkillDomainCompatibility } from '../compatibility.mjs';
|
|
65
|
+
import {
|
|
66
|
+
ARCHITECT_DEFAULT_TOKEN_BUDGET,
|
|
67
|
+
ARCHITECT_DEFAULT_TIMEOUT_MS,
|
|
68
|
+
ARCHITECT_MIN_TOKEN_BUDGET,
|
|
69
|
+
ARCHITECT_MAX_TOKEN_BUDGET,
|
|
70
|
+
ARCHITECT_MIN_TIMEOUT_MS,
|
|
71
|
+
ARCHITECT_MAX_TIMEOUT_MS,
|
|
72
|
+
recommendArchitecture,
|
|
73
|
+
formatArchitectureRecommendation,
|
|
74
|
+
readArchitectPreferenceState,
|
|
75
|
+
createUpdatedArchitectPreference,
|
|
76
|
+
shouldApplyRepeatedOverridePreference,
|
|
77
|
+
writeArchitectPreferenceState,
|
|
78
|
+
} from '../architect.mjs';
|
|
65
79
|
|
|
66
80
|
export { REPO_ROOT } from '../constants.mjs';
|
|
67
81
|
|
|
@@ -83,6 +97,9 @@ export function parseInitArguments(commandArguments) {
|
|
|
83
97
|
docsLang: 'en',
|
|
84
98
|
docsLangProvided: false,
|
|
85
99
|
projectConfig: undefined,
|
|
100
|
+
projectDescription: '',
|
|
101
|
+
architectTokenBudget: ARCHITECT_DEFAULT_TOKEN_BUDGET,
|
|
102
|
+
architectTimeoutMs: ARCHITECT_DEFAULT_TIMEOUT_MS,
|
|
86
103
|
runtimeEnv: 'auto',
|
|
87
104
|
runtimeEnvProvided: false,
|
|
88
105
|
};
|
|
@@ -242,6 +259,55 @@ export function parseInitArguments(commandArguments) {
|
|
|
242
259
|
continue;
|
|
243
260
|
}
|
|
244
261
|
|
|
262
|
+
if (currentArgument === '--project-description') {
|
|
263
|
+
parsedInitOptions.projectDescription = commandArguments[argumentIndex + 1] || '';
|
|
264
|
+
argumentIndex += 1;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (currentArgument.startsWith('--project-description=')) {
|
|
269
|
+
parsedInitOptions.projectDescription = currentArgument.split('=')[1] || '';
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (currentArgument === '--architect-token-budget') {
|
|
274
|
+
const rawTokenBudget = Number.parseInt(commandArguments[argumentIndex + 1], 10);
|
|
275
|
+
if (Number.isNaN(rawTokenBudget)) {
|
|
276
|
+
throw new Error('--architect-token-budget must be a number');
|
|
277
|
+
}
|
|
278
|
+
parsedInitOptions.architectTokenBudget = rawTokenBudget;
|
|
279
|
+
argumentIndex += 1;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (currentArgument.startsWith('--architect-token-budget=')) {
|
|
284
|
+
const rawTokenBudget = Number.parseInt(currentArgument.split('=')[1], 10);
|
|
285
|
+
if (Number.isNaN(rawTokenBudget)) {
|
|
286
|
+
throw new Error('--architect-token-budget must be a number');
|
|
287
|
+
}
|
|
288
|
+
parsedInitOptions.architectTokenBudget = rawTokenBudget;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (currentArgument === '--architect-timeout-ms') {
|
|
293
|
+
const rawTimeoutMs = Number.parseInt(commandArguments[argumentIndex + 1], 10);
|
|
294
|
+
if (Number.isNaN(rawTimeoutMs)) {
|
|
295
|
+
throw new Error('--architect-timeout-ms must be a number');
|
|
296
|
+
}
|
|
297
|
+
parsedInitOptions.architectTimeoutMs = rawTimeoutMs;
|
|
298
|
+
argumentIndex += 1;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (currentArgument.startsWith('--architect-timeout-ms=')) {
|
|
303
|
+
const rawTimeoutMs = Number.parseInt(currentArgument.split('=')[1], 10);
|
|
304
|
+
if (Number.isNaN(rawTimeoutMs)) {
|
|
305
|
+
throw new Error('--architect-timeout-ms must be a number');
|
|
306
|
+
}
|
|
307
|
+
parsedInitOptions.architectTimeoutMs = rawTimeoutMs;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
|
|
245
311
|
if (currentArgument === '--runtime-env') {
|
|
246
312
|
parsedInitOptions.runtimeEnv = commandArguments[argumentIndex + 1] || 'auto';
|
|
247
313
|
parsedInitOptions.runtimeEnvProvided = true;
|
|
@@ -272,6 +338,18 @@ export function parseInitArguments(commandArguments) {
|
|
|
272
338
|
throw new Error('--runtime-env must be one of: auto, linux-wsl, linux, windows, macos');
|
|
273
339
|
}
|
|
274
340
|
|
|
341
|
+
if (!Number.isInteger(parsedInitOptions.architectTokenBudget)
|
|
342
|
+
|| parsedInitOptions.architectTokenBudget < ARCHITECT_MIN_TOKEN_BUDGET
|
|
343
|
+
|| parsedInitOptions.architectTokenBudget > ARCHITECT_MAX_TOKEN_BUDGET) {
|
|
344
|
+
throw new Error(`--architect-token-budget must be an integer between ${ARCHITECT_MIN_TOKEN_BUDGET} and ${ARCHITECT_MAX_TOKEN_BUDGET}`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (!Number.isInteger(parsedInitOptions.architectTimeoutMs)
|
|
348
|
+
|| parsedInitOptions.architectTimeoutMs < ARCHITECT_MIN_TIMEOUT_MS
|
|
349
|
+
|| parsedInitOptions.architectTimeoutMs > ARCHITECT_MAX_TIMEOUT_MS) {
|
|
350
|
+
throw new Error(`--architect-timeout-ms must be an integer between ${ARCHITECT_MIN_TIMEOUT_MS} and ${ARCHITECT_MAX_TIMEOUT_MS}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
275
353
|
parsedInitOptions.docsLang = normalizedDocsLanguage;
|
|
276
354
|
parsedInitOptions.runtimeEnv = normalizedRuntimeEnvironment;
|
|
277
355
|
parsedInitOptions.tokenAgent = normalizeAgentName(parsedInitOptions.tokenAgent);
|
|
@@ -595,7 +673,11 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
595
673
|
let selectedAdditionalStackFileNames = [];
|
|
596
674
|
let selectedAdditionalBlueprintFileNames = [];
|
|
597
675
|
|
|
598
|
-
|
|
676
|
+
let architectureRecommendation = null;
|
|
677
|
+
let architectPreferenceState = await readArchitectPreferenceState();
|
|
678
|
+
let architectPreferenceUpdated = false;
|
|
679
|
+
|
|
680
|
+
const shouldRunArchitectureRecommendation = !selectedStackFileNameFromOption
|
|
599
681
|
&& !selectedPreset?.stack
|
|
600
682
|
&& !shouldAutoApplyDetectedStack
|
|
601
683
|
&& !selectedProfilePack?.defaultStackFileName
|
|
@@ -609,82 +691,113 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
609
691
|
selectedAdditionalStackFileNames = projectDetection.secondaryStackFileNames || [];
|
|
610
692
|
}
|
|
611
693
|
|
|
612
|
-
if (
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
userInterface
|
|
694
|
+
if (shouldRunArchitectureRecommendation) {
|
|
695
|
+
let architectureProjectDescription = String(initOptions.projectDescription || '').trim();
|
|
696
|
+
|
|
697
|
+
if (!architectureProjectDescription && isInteractiveSession) {
|
|
698
|
+
architectureProjectDescription = (await userInterface.question(
|
|
699
|
+
'\nDescribe your project in one short paragraph for architecture recommendation: '
|
|
700
|
+
)).trim();
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
if (!architectureProjectDescription) {
|
|
704
|
+
architectureProjectDescription = `A software project named ${path.basename(resolvedTargetDirectoryPath)}.`;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
architectureRecommendation = recommendArchitecture({
|
|
708
|
+
projectDescription: architectureProjectDescription,
|
|
709
|
+
projectDetection,
|
|
710
|
+
stackFileNames,
|
|
711
|
+
blueprintFileNames,
|
|
712
|
+
tokenBudget: initOptions.architectTokenBudget,
|
|
713
|
+
timeoutMs: initOptions.architectTimeoutMs,
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
architectureRecommendation.userVeto = {
|
|
717
|
+
applied: false,
|
|
718
|
+
selectedStackFileName: architectureRecommendation.recommendedStackFileName,
|
|
719
|
+
selectedBlueprintFileName: architectureRecommendation.recommendedBlueprintFileName,
|
|
720
|
+
source: 'recommendation',
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
console.log(formatArchitectureRecommendation(architectureRecommendation));
|
|
724
|
+
|
|
725
|
+
const shouldSkipRecommendationDebate = shouldApplyRepeatedOverridePreference(
|
|
726
|
+
architectPreferenceState,
|
|
727
|
+
architectureRecommendation.recommendedStackFileName
|
|
617
728
|
);
|
|
618
|
-
const selectedProjectScopeKey = resolveProjectScopeKeyFromLabel(selectedProjectScopeLabel);
|
|
619
|
-
const scopeStackFilter = PROJECT_SCOPE_STACK_FILTERS[selectedProjectScopeKey];
|
|
620
|
-
const scopedStackFileNames = filterStackFileNamesByCandidates(stackFileNames, scopeStackFilter);
|
|
621
729
|
|
|
622
|
-
if (
|
|
623
|
-
|
|
624
|
-
|
|
730
|
+
if (shouldSkipRecommendationDebate) {
|
|
731
|
+
architectureRecommendation.failureModes.repeatedOverride = true;
|
|
732
|
+
selectedManualStackFileName = stackFileNames.includes(architectPreferenceState.preferredStackFileName)
|
|
733
|
+
? architectPreferenceState.preferredStackFileName
|
|
734
|
+
: architectureRecommendation.recommendedStackFileName;
|
|
735
|
+
selectedManualBlueprintFileName = blueprintFileNames.includes(architectPreferenceState.preferredBlueprintFileName)
|
|
736
|
+
? architectPreferenceState.preferredBlueprintFileName
|
|
737
|
+
: architectureRecommendation.recommendedBlueprintFileName;
|
|
738
|
+
architectureRecommendation.userVeto = {
|
|
739
|
+
applied: true,
|
|
740
|
+
selectedStackFileName: selectedManualStackFileName,
|
|
741
|
+
selectedBlueprintFileName: selectedManualBlueprintFileName,
|
|
742
|
+
source: 'saved-preference',
|
|
743
|
+
};
|
|
744
|
+
console.log(
|
|
745
|
+
`Repeated override preference detected. Applying ${toTitleCase(selectedManualStackFileName)} + ${toTitleCase(selectedManualBlueprintFileName)} without additional debate.`
|
|
746
|
+
);
|
|
747
|
+
} else if (!isInteractiveSession) {
|
|
748
|
+
selectedManualStackFileName = architectureRecommendation.recommendedStackFileName;
|
|
749
|
+
selectedManualBlueprintFileName = architectureRecommendation.recommendedBlueprintFileName;
|
|
750
|
+
} else {
|
|
751
|
+
const shouldApplyRecommendedArchitecture = await askYesNo(
|
|
752
|
+
'Apply this architecture recommendation?',
|
|
625
753
|
userInterface,
|
|
626
|
-
|
|
754
|
+
true
|
|
627
755
|
);
|
|
628
756
|
|
|
629
|
-
if (
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const
|
|
634
|
-
'
|
|
635
|
-
|
|
757
|
+
if (shouldApplyRecommendedArchitecture) {
|
|
758
|
+
selectedManualStackFileName = architectureRecommendation.recommendedStackFileName;
|
|
759
|
+
selectedManualBlueprintFileName = architectureRecommendation.recommendedBlueprintFileName;
|
|
760
|
+
} else {
|
|
761
|
+
const vetoStackFileName = await askStackSelection(
|
|
762
|
+
'User veto received. Select stack to apply immediately:',
|
|
763
|
+
stackFileNames,
|
|
636
764
|
userInterface
|
|
637
765
|
);
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
766
|
+
|
|
767
|
+
const vetoBlueprintCandidates = filterBlueprintFileNamesByCandidates(
|
|
768
|
+
blueprintFileNames,
|
|
769
|
+
[BLUEPRINT_RECOMMENDATIONS[vetoStackFileName]].filter(Boolean)
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
const vetoBlueprintFileName = await askBlueprintSelection(
|
|
773
|
+
'Select blueprint to apply immediately (no further debate):',
|
|
774
|
+
vetoBlueprintCandidates,
|
|
641
775
|
userInterface
|
|
642
776
|
);
|
|
643
777
|
|
|
644
|
-
selectedManualStackFileName =
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
778
|
+
selectedManualStackFileName = vetoStackFileName;
|
|
779
|
+
selectedManualBlueprintFileName = vetoBlueprintFileName;
|
|
780
|
+
architectureRecommendation.userVeto = {
|
|
781
|
+
applied: true,
|
|
782
|
+
selectedStackFileName: vetoStackFileName,
|
|
783
|
+
selectedBlueprintFileName: vetoBlueprintFileName,
|
|
784
|
+
source: 'interactive-veto',
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
architectPreferenceState = createUpdatedArchitectPreference(architectPreferenceState, {
|
|
788
|
+
selectedStackFileName: vetoStackFileName,
|
|
789
|
+
selectedBlueprintFileName: vetoBlueprintFileName,
|
|
790
|
+
});
|
|
791
|
+
architectPreferenceUpdated = true;
|
|
648
792
|
|
|
649
|
-
if (
|
|
650
|
-
|
|
651
|
-
blueprintFileNames,
|
|
652
|
-
WEB_FRONTEND_BLUEPRINT_CANDIDATES
|
|
653
|
-
);
|
|
654
|
-
const selectableBackendBlueprints = filterBlueprintFileNamesByCandidates(
|
|
655
|
-
blueprintFileNames,
|
|
656
|
-
WEB_BACKEND_BLUEPRINT_CANDIDATES
|
|
657
|
-
);
|
|
658
|
-
|
|
659
|
-
const selectedFrontendBlueprintFileName = await askBlueprintSelection(
|
|
660
|
-
'Which frontend blueprint should guide UI architecture?',
|
|
661
|
-
selectableFrontendBlueprints,
|
|
662
|
-
userInterface
|
|
663
|
-
);
|
|
664
|
-
const selectedBackendBlueprintFileName = await askBlueprintSelection(
|
|
665
|
-
'Which backend blueprint should guide service architecture?',
|
|
666
|
-
selectableBackendBlueprints,
|
|
667
|
-
userInterface
|
|
668
|
-
);
|
|
669
|
-
|
|
670
|
-
selectedManualBlueprintFileName = selectedBackendBlueprintFileName;
|
|
671
|
-
if (selectedFrontendBlueprintFileName && selectedFrontendBlueprintFileName !== selectedBackendBlueprintFileName) {
|
|
672
|
-
selectedAdditionalBlueprintFileNames = [selectedFrontendBlueprintFileName];
|
|
673
|
-
}
|
|
793
|
+
if (architectPreferenceState.overrideCount >= 2) {
|
|
794
|
+
architectureRecommendation.failureModes.repeatedOverride = true;
|
|
674
795
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
scopedStackFileNames,
|
|
679
|
-
userInterface
|
|
796
|
+
|
|
797
|
+
console.log(
|
|
798
|
+
`Veto applied. Proceeding with ${toTitleCase(vetoStackFileName)} + ${toTitleCase(vetoBlueprintFileName)} without recommendation loops.`
|
|
680
799
|
);
|
|
681
800
|
}
|
|
682
|
-
} else {
|
|
683
|
-
selectedManualStackFileName = await askStackSelection(
|
|
684
|
-
'Which stack should this governance pack target?',
|
|
685
|
-
scopedStackFileNames,
|
|
686
|
-
userInterface
|
|
687
|
-
);
|
|
688
801
|
}
|
|
689
802
|
}
|
|
690
803
|
|
|
@@ -724,6 +837,20 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
724
837
|
)
|
|
725
838
|
];
|
|
726
839
|
|
|
840
|
+
if (architectureRecommendation) {
|
|
841
|
+
architectureRecommendation.appliedStackFileName = selectedResolvedStackFileName;
|
|
842
|
+
architectureRecommendation.appliedBlueprintFileName = selectedResolvedBlueprintFileName;
|
|
843
|
+
|
|
844
|
+
if (!architectureRecommendation.userVeto) {
|
|
845
|
+
architectureRecommendation.userVeto = {
|
|
846
|
+
applied: false,
|
|
847
|
+
selectedStackFileName: selectedResolvedStackFileName,
|
|
848
|
+
selectedBlueprintFileName: selectedResolvedBlueprintFileName,
|
|
849
|
+
source: 'recommendation',
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
727
854
|
const derivedAdditionalBlueprintFileNames = deriveAdditionalBlueprintFileNamesFromStacks(
|
|
728
855
|
selectedAdditionalStackFileNames,
|
|
729
856
|
blueprintFileNames,
|
|
@@ -916,8 +1043,13 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
916
1043
|
adapters: memoryContinuityState?.adapters || [],
|
|
917
1044
|
stateFile: isMemoryContinuityEnabled ? '.agent-context/state/memory-continuity.json' : null,
|
|
918
1045
|
},
|
|
1046
|
+
architectRecommendation: architectureRecommendation,
|
|
919
1047
|
});
|
|
920
1048
|
|
|
1049
|
+
if (architectPreferenceUpdated && architectPreferenceState) {
|
|
1050
|
+
await writeArchitectPreferenceState(architectPreferenceState);
|
|
1051
|
+
}
|
|
1052
|
+
|
|
921
1053
|
console.log('\nInitialization complete.');
|
|
922
1054
|
console.log(`- Target directory: ${resolvedTargetDirectoryPath}`);
|
|
923
1055
|
console.log(`- Profile: ${selectedProfile.displayName}`);
|
|
@@ -927,6 +1059,16 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
927
1059
|
if (selectedProfilePack) {
|
|
928
1060
|
console.log(`- Team profile pack: ${selectedProfilePack.displayName}`);
|
|
929
1061
|
}
|
|
1062
|
+
if (architectureRecommendation) {
|
|
1063
|
+
console.log(
|
|
1064
|
+
`- Architect recommendation: ${toTitleCase(architectureRecommendation.recommendedStackFileName)} + ${toTitleCase(architectureRecommendation.recommendedBlueprintFileName)} (${architectureRecommendation.confidenceLabel})`
|
|
1065
|
+
);
|
|
1066
|
+
if (architectureRecommendation.userVeto?.applied) {
|
|
1067
|
+
console.log(
|
|
1068
|
+
`- User veto path: applied (${toTitleCase(architectureRecommendation.userVeto.selectedStackFileName)} + ${toTitleCase(architectureRecommendation.userVeto.selectedBlueprintFileName)})`
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
930
1072
|
console.log(`- Stack: ${toTitleCase(selectedResolvedStackFileName)}`);
|
|
931
1073
|
if (selectedAdditionalStackFileNames.length > 0) {
|
|
932
1074
|
console.log(`- Additional stacks: ${selectedAdditionalStackFileNames.map((stackFileName) => toTitleCase(stackFileName)).join(', ')}`);
|
package/lib/cli/compiler.mjs
CHANGED
|
@@ -55,6 +55,7 @@ export async function writeOnboardingReport({
|
|
|
55
55
|
operationMode = 'init',
|
|
56
56
|
tokenOptimization = undefined,
|
|
57
57
|
memoryContinuity = undefined,
|
|
58
|
+
architectRecommendation = null,
|
|
58
59
|
}) {
|
|
59
60
|
const onboardingReportPath = path.join(targetDirectoryPath, '.agent-context', 'state', 'onboarding-report.json');
|
|
60
61
|
const resolvedTokenOptimization = typeof tokenOptimization === 'undefined'
|
|
@@ -86,6 +87,7 @@ export async function writeOnboardingReport({
|
|
|
86
87
|
runtimeEnvironment,
|
|
87
88
|
tokenOptimization: resolvedTokenOptimization,
|
|
88
89
|
memoryContinuity: resolvedMemoryContinuity,
|
|
90
|
+
architectRecommendation,
|
|
89
91
|
autoDetection: {
|
|
90
92
|
recommendedStack: projectDetection.recommendedStackFileName,
|
|
91
93
|
recommendedAdditionalStacks: projectDetection.secondaryStackFileNames || [],
|
package/lib/cli/utils.mjs
CHANGED
|
@@ -29,7 +29,7 @@ export function printUsage() {
|
|
|
29
29
|
console.log('');
|
|
30
30
|
console.log('Usage:');
|
|
31
31
|
console.log(' agentic-senior-core launch');
|
|
32
|
-
console.log(' agentic-senior-core init [target-directory] [--preset <name>] [--profile <beginner|balanced|strict>] [--profile-pack <name>] [--stack <name>] [--blueprint <name>] [--ci <true|false>] [--newbie] [--token-optimize] [--no-token-optimize] [--token-agent <name>] [--memory-continuity] [--no-memory-continuity] [--scaffold-docs] [--no-scaffold-docs] [--docs-lang <en|id>] [--project-config <path>] [--runtime-env <auto|linux-wsl|linux|windows|macos>]');
|
|
32
|
+
console.log(' agentic-senior-core init [target-directory] [--preset <name>] [--profile <beginner|balanced|strict>] [--profile-pack <name>] [--stack <name>] [--blueprint <name>] [--project-description <text>] [--architect-token-budget <number>] [--architect-timeout-ms <number>] [--ci <true|false>] [--newbie] [--token-optimize] [--no-token-optimize] [--token-agent <name>] [--memory-continuity] [--no-memory-continuity] [--scaffold-docs] [--no-scaffold-docs] [--docs-lang <en|id>] [--project-config <path>] [--runtime-env <auto|linux-wsl|linux|windows|macos>]');
|
|
33
33
|
console.log(' agentic-senior-core upgrade [target-directory] [--dry-run] [--yes] [--mcp-template]');
|
|
34
34
|
console.log(' agentic-senior-core optimize [target-directory] [--agent <copilot|claude|cursor|windsurf|gemini|codex|cline>] [--enable|--disable] [--show]');
|
|
35
35
|
console.log(' agentic-senior-core mcp');
|
|
@@ -46,6 +46,9 @@ export function printUsage() {
|
|
|
46
46
|
console.log(' --newbie Legacy alias for --profile beginner');
|
|
47
47
|
console.log(' --stack Override stack selection');
|
|
48
48
|
console.log(' --blueprint Override blueprint selection');
|
|
49
|
+
console.log(' --project-description Architecture intent text used for stack/blueprint recommendation');
|
|
50
|
+
console.log(' --architect-token-budget Max token estimate used by recommendation research (default: 900)');
|
|
51
|
+
console.log(' --architect-timeout-ms Max recommendation research time in milliseconds (default: 1500)');
|
|
49
52
|
console.log(' --ci Override CI/CD guardrails (true|false)');
|
|
50
53
|
console.log(' --token-optimize Explicitly enable token optimization policy during init (default behavior)');
|
|
51
54
|
console.log(' --token-agent Set token optimization agent target (copilot, claude, cursor, windsurf, gemini, codex, cline)');
|
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@ const REQUIRED_FILES = [
|
|
|
20
20
|
'docs/roadmap.md',
|
|
21
21
|
'docs/v1.7-issue-breakdown.md',
|
|
22
22
|
'docs/v1.7-execution-playbook.md',
|
|
23
|
+
'.agent-context/rules/frontend-architecture.md',
|
|
23
24
|
'.agent-context/review-checklists/frontend-usability.md',
|
|
24
25
|
'.agent-context/review-checklists/frontend-excellence-rubric.md',
|
|
25
26
|
];
|
|
@@ -43,9 +44,19 @@ const REQUIRED_EXCELLENCE_RUBRIC_SNIPPETS = [
|
|
|
43
44
|
'Typography Quality',
|
|
44
45
|
'Color System Diversity and Contrast',
|
|
45
46
|
'Interaction Choreography',
|
|
47
|
+
'UX Narrative and Conversion Clarity',
|
|
48
|
+
'Template Diversity and Originality',
|
|
49
|
+
'Low-Diversity Template Output Policy',
|
|
46
50
|
'Awwwards-level reference quality',
|
|
47
51
|
];
|
|
48
52
|
|
|
53
|
+
const REQUIRED_FRONTEND_RULE_SNIPPETS = [
|
|
54
|
+
'Frontend Designer Mode (Auto Activation)',
|
|
55
|
+
'UI scope trigger signals',
|
|
56
|
+
'visual intent, interaction quality, and conversion clarity',
|
|
57
|
+
'template-only repetitive outputs',
|
|
58
|
+
];
|
|
59
|
+
|
|
49
60
|
function assertFileExists(relativeFilePath, failures) {
|
|
50
61
|
const absoluteFilePath = resolve(REPOSITORY_ROOT, relativeFilePath);
|
|
51
62
|
if (!existsSync(absoluteFilePath)) {
|
|
@@ -69,6 +80,7 @@ function runAudit() {
|
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
const roadmapPath = 'docs/roadmap.md';
|
|
83
|
+
const frontendRulePath = '.agent-context/rules/frontend-architecture.md';
|
|
72
84
|
const checklistPath = '.agent-context/review-checklists/frontend-usability.md';
|
|
73
85
|
const excellenceRubricPath = '.agent-context/review-checklists/frontend-excellence-rubric.md';
|
|
74
86
|
|
|
@@ -82,6 +94,11 @@ function runAudit() {
|
|
|
82
94
|
assertContains('Checklist', checklistPath, checklistContent, REQUIRED_CHECKLIST_SNIPPETS, failures);
|
|
83
95
|
}
|
|
84
96
|
|
|
97
|
+
if (existsSync(resolve(REPOSITORY_ROOT, frontendRulePath))) {
|
|
98
|
+
const frontendRuleContent = readFileSync(resolve(REPOSITORY_ROOT, frontendRulePath), 'utf8');
|
|
99
|
+
assertContains('Frontend rule', frontendRulePath, frontendRuleContent, REQUIRED_FRONTEND_RULE_SNIPPETS, failures);
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
if (existsSync(resolve(REPOSITORY_ROOT, excellenceRubricPath))) {
|
|
86
103
|
const excellenceRubricContent = readFileSync(resolve(REPOSITORY_ROOT, excellenceRubricPath), 'utf8');
|
|
87
104
|
assertContains(
|
package/scripts/release-gate.mjs
CHANGED
|
@@ -42,6 +42,9 @@ const REQUIRED_FRONTEND_EXCELLENCE_RUBRIC_SNIPPETS = [
|
|
|
42
42
|
'Typography Quality',
|
|
43
43
|
'Color System Diversity and Contrast',
|
|
44
44
|
'Interaction Choreography',
|
|
45
|
+
'UX Narrative and Conversion Clarity',
|
|
46
|
+
'Template Diversity and Originality',
|
|
47
|
+
'Low-Diversity Template Output Policy',
|
|
45
48
|
'Awwwards-level reference quality',
|
|
46
49
|
];
|
|
47
50
|
const BENCHMARK_GATE_SCRIPT_PATH = 'scripts/benchmark-gate.mjs';
|