@ryuenn3123/agentic-senior-core 2.5.8 → 2.5.9

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-04-17T07:42:53.417Z",
2
+ "generatedAt": "2026-04-17T07:58:05.182Z",
3
3
  "reportName": "memory-continuity-benchmark",
4
4
  "schemaVersion": "1.0.0",
5
5
  "passed": true,
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.8
3
+ Generated by Agentic-Senior-Core CLI v2.5.9
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.8
3
+ Generated by Agentic-Senior-Core CLI v2.5.9
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
- const shouldAskManualStackSelection = !selectedStackFileNameFromOption
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 (shouldAskManualStackSelection) {
613
- const selectedProjectScopeLabel = await askChoice(
614
- 'What are you building?',
615
- PROJECT_SCOPE_CHOICES.map((scopeChoice) => scopeChoice.label),
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 (selectedProjectScopeKey === 'web-application') {
623
- const usesSplitWebStacks = await askYesNo(
624
- 'For this web project, do frontend and backend use different primary stacks?',
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
- false
754
+ true
627
755
  );
628
756
 
629
- if (usesSplitWebStacks) {
630
- const selectableFrontendStacks = filterStackFileNamesByCandidates(scopedStackFileNames, WEB_FRONTEND_STACK_CANDIDATES);
631
- const selectableBackendStacks = filterStackFileNamesByCandidates(scopedStackFileNames, WEB_BACKEND_STACK_CANDIDATES);
632
-
633
- const selectedFrontendStackFileName = await askStackSelection(
634
- 'Which frontend stack should this governance pack target?',
635
- selectableFrontendStacks,
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
- const selectedBackendStackFileName = await askStackSelection(
639
- 'Which backend stack should this governance pack target?',
640
- selectableBackendStacks,
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 = selectedBackendStackFileName;
645
- if (selectedFrontendStackFileName && selectedFrontendStackFileName !== selectedBackendStackFileName) {
646
- selectedAdditionalStackFileNames = [selectedFrontendStackFileName];
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 (!selectedBlueprintFileNameFromOption && !selectedPreset?.blueprint) {
650
- const selectableFrontendBlueprints = filterBlueprintFileNamesByCandidates(
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
- } else {
676
- selectedManualStackFileName = await askStackSelection(
677
- 'Which stack should this governance pack target?',
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(', ')}`);
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuenn3123/agentic-senior-core",
3
- "version": "2.5.8",
3
+ "version": "2.5.9",
4
4
  "type": "module",
5
5
  "description": "Force your AI Agent to code like a Staff Engineer, not a Junior.",
6
6
  "bin": {