@ryuenn3123/agentic-senior-core 3.0.19 → 3.0.20

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.
Files changed (77) hide show
  1. package/.agent-context/prompts/bootstrap-design.md +84 -103
  2. package/.agent-context/prompts/init-project.md +32 -100
  3. package/.agent-context/prompts/refactor.md +22 -44
  4. package/.agent-context/prompts/review-code.md +28 -52
  5. package/.agent-context/review-checklists/architecture-review.md +31 -62
  6. package/.agent-context/review-checklists/pr-checklist.md +74 -108
  7. package/.agent-context/rules/api-docs.md +18 -206
  8. package/.agent-context/rules/architecture.md +40 -207
  9. package/.agent-context/rules/database-design.md +10 -199
  10. package/.agent-context/rules/docker-runtime.md +5 -5
  11. package/.agent-context/rules/efficiency-vs-hype.md +11 -149
  12. package/.agent-context/rules/error-handling.md +9 -231
  13. package/.agent-context/rules/event-driven.md +17 -221
  14. package/.agent-context/rules/frontend-architecture.md +66 -119
  15. package/.agent-context/rules/git-workflow.md +1 -1
  16. package/.agent-context/rules/microservices.md +28 -161
  17. package/.agent-context/rules/naming-conv.md +8 -138
  18. package/.agent-context/rules/performance.md +9 -175
  19. package/.agent-context/rules/realtime.md +11 -44
  20. package/.agent-context/rules/security.md +11 -295
  21. package/.agent-context/rules/testing.md +9 -174
  22. package/.agent-context/state/benchmark-analysis.json +3 -3
  23. package/.agent-context/state/memory-continuity-benchmark.json +1 -1
  24. package/.agent-context/state/onboarding-report.json +71 -11
  25. package/.agents/workflows/init-project.md +7 -24
  26. package/.agents/workflows/refactor.md +7 -24
  27. package/.agents/workflows/review-code.md +7 -24
  28. package/.cursorrules +22 -21
  29. package/.gemini/instructions.md +2 -2
  30. package/.github/copilot-instructions.md +2 -2
  31. package/.instructions.md +112 -213
  32. package/.windsurfrules +22 -21
  33. package/AGENTS.md +4 -4
  34. package/CONTRIBUTING.md +13 -22
  35. package/README.md +6 -20
  36. package/lib/cli/commands/init.mjs +102 -148
  37. package/lib/cli/commands/launch.mjs +3 -3
  38. package/lib/cli/commands/optimize.mjs +14 -4
  39. package/lib/cli/commands/upgrade.mjs +25 -23
  40. package/lib/cli/compiler.mjs +96 -62
  41. package/lib/cli/constants.mjs +28 -136
  42. package/lib/cli/detector/design-evidence.mjs +189 -6
  43. package/lib/cli/detector.mjs +6 -7
  44. package/lib/cli/init-detection-flow.mjs +10 -93
  45. package/lib/cli/init-selection.mjs +2 -68
  46. package/lib/cli/project-scaffolder/constants.mjs +1 -1
  47. package/lib/cli/project-scaffolder/design-contract.mjs +162 -108
  48. package/lib/cli/project-scaffolder/discovery.mjs +36 -82
  49. package/lib/cli/project-scaffolder/prompt-builders.mjs +41 -55
  50. package/lib/cli/project-scaffolder/storage.mjs +0 -2
  51. package/lib/cli/token-optimization.mjs +1 -1
  52. package/lib/cli/utils.mjs +75 -9
  53. package/package.json +2 -2
  54. package/scripts/detection-benchmark.mjs +4 -15
  55. package/scripts/documentation-boundary-audit.mjs +9 -9
  56. package/scripts/explain-on-demand-audit.mjs +11 -11
  57. package/scripts/forbidden-content-check.mjs +9 -9
  58. package/scripts/frontend-usability-audit.mjs +45 -35
  59. package/scripts/llm-judge.mjs +1 -1
  60. package/scripts/mcp-server/constants.mjs +2 -2
  61. package/scripts/mcp-server/tool-registry.mjs +1 -1
  62. package/scripts/release-gate/audit-checks.mjs +4 -4
  63. package/scripts/release-gate/static-checks.mjs +5 -5
  64. package/scripts/release-gate.mjs +1 -1
  65. package/scripts/rules-guardian-audit.mjs +14 -13
  66. package/scripts/single-source-lazy-loading-audit.mjs +3 -3
  67. package/scripts/sync-thin-adapters.mjs +5 -5
  68. package/scripts/ui-design-judge/design-execution-summary.mjs +27 -1
  69. package/scripts/ui-design-judge/prompting.mjs +4 -4
  70. package/scripts/ui-design-judge/reporting.mjs +2 -1
  71. package/scripts/ui-design-judge/rubric-calibration.mjs +8 -5
  72. package/scripts/ui-design-judge/rubric-goldset.json +2 -2
  73. package/scripts/ui-design-judge.mjs +70 -6
  74. package/scripts/validate/config.mjs +138 -48
  75. package/scripts/validate/coverage-checks.mjs +32 -7
  76. package/scripts/validate.mjs +8 -4
  77. package/lib/cli/architect.mjs +0 -431
@@ -3,6 +3,7 @@ import { join, relative } from 'node:path';
3
3
  import {
4
4
  COMPLIANCE_ALIAS_TERMS,
5
5
  COMPLIANCE_TERMINOLOGY_BOUNDARY_PATHS,
6
+ FORBIDDEN_ACTIVE_BIAS_ANCHOR_SNIPPETS,
6
7
  FORMAL_ARTIFACT_PATHS,
7
8
  FORBIDDEN_TEMPLATE_BOOTSTRAP_SNIPPETS,
8
9
  REQUIRED_COMPLIANCE_CANONICAL_SNIPPETS,
@@ -12,7 +13,7 @@ import {
12
13
  REQUIRED_DEVELOPER_FIRST_MENTION_PATTERNS,
13
14
  REQUIRED_DOCKER_RUNTIME_AUTOMATION_SNIPPETS,
14
15
  REQUIRED_HUMAN_WRITING_SNIPPETS,
15
- REQUIRED_STACK_RESEARCH_ENGINE_SNIPPETS,
16
+ REQUIRED_STACK_DECISION_BOUNDARY_SNIPPETS,
16
17
  REQUIRED_TEMPLATE_FREE_BOOTSTRAP_SNIPPETS,
17
18
  REQUIRED_TERMINOLOGY_ROW_PATTERNS,
18
19
  REQUIRED_TERMINOLOGY_RULE_SNIPPET,
@@ -86,7 +87,7 @@ export async function validateTerminologyMapping(context) {
86
87
  fail(`${TERMINOLOGY_REFERENCE_DOCUMENT_PATH} must define first-mention canonical term rule`);
87
88
  }
88
89
 
89
- if (terminologyReferenceContent.includes('Compliance and audit artifacts must keep canonical enterprise terminology')) {
90
+ if (terminologyReferenceContent.includes('Formal policy and audit artifacts must keep canonical terminology')) {
90
91
  pass(`${TERMINOLOGY_REFERENCE_DOCUMENT_PATH} defines compliance terminology boundary`);
91
92
  } else {
92
93
  fail(`${TERMINOLOGY_REFERENCE_DOCUMENT_PATH} must define compliance terminology boundary`);
@@ -191,12 +192,12 @@ export async function validateDetectionTransparencyCoverage(context) {
191
192
  });
192
193
  }
193
194
 
194
- export async function validateStackResearchEngineCoverage(context) {
195
+ export async function validateStackDecisionBoundaryCoverage(context) {
195
196
  await validateSnippetCoverage({
196
- heading: 'Checking stack research engine coverage...',
197
- coverageRules: REQUIRED_STACK_RESEARCH_ENGINE_SNIPPETS,
198
- missingLabel: 'stack research source',
199
- snippetLabel: 'stack research snippet',
197
+ heading: 'Checking stack decision boundary coverage...',
198
+ coverageRules: REQUIRED_STACK_DECISION_BOUNDARY_SNIPPETS,
199
+ missingLabel: 'stack decision boundary source',
200
+ snippetLabel: 'stack decision boundary snippet',
200
201
  context,
201
202
  });
202
203
  }
@@ -303,6 +304,30 @@ export async function validateDeterministicBoundaryEnforcementCoverage(context)
303
304
  });
304
305
  }
305
306
 
307
+ export async function validateRulesOnlyActiveSurfaceCoverage(context) {
308
+ const { ROOT_DIR, fileExists, readTextFile, pass, fail } = context;
309
+
310
+ console.log('\nChecking rules-only active surface cleanup...');
311
+
312
+ for (const forbiddenRule of FORBIDDEN_ACTIVE_BIAS_ANCHOR_SNIPPETS) {
313
+ const absoluteForbiddenPath = join(ROOT_DIR, forbiddenRule.path);
314
+
315
+ if (!(await fileExists(absoluteForbiddenPath))) {
316
+ fail(`Missing rules-only active surface source: ${forbiddenRule.path}`);
317
+ continue;
318
+ }
319
+
320
+ const forbiddenContent = await readTextFile(absoluteForbiddenPath);
321
+ for (const forbiddenSnippet of forbiddenRule.snippets) {
322
+ if (forbiddenContent.includes(forbiddenSnippet)) {
323
+ fail(`${forbiddenRule.path} must not include active bias-anchor snippet: ${forbiddenSnippet}`);
324
+ } else {
325
+ pass(`${forbiddenRule.path} excludes active bias-anchor snippet: ${forbiddenSnippet}`);
326
+ }
327
+ }
328
+ }
329
+ }
330
+
306
331
  export async function validateHumanWritingGovernance(context) {
307
332
  const { ROOT_DIR, fileExists, readTextFile, pass, fail } = context;
308
333
 
@@ -19,7 +19,6 @@ import { fileURLToPath } from 'node:url';
19
19
  import {
20
20
  ALLOWED_SEVERITIES,
21
21
  OVERRIDE_WARNING_WINDOW_DAYS,
22
- REQUIRED_STACK_RESEARCH_ENGINE_SNIPPETS,
23
22
  } from './validate/config.mjs';
24
23
  import {
25
24
  validateDependencyFreshnessAutomationCoverage,
@@ -28,8 +27,9 @@ import {
28
27
  validateDockerRuntimeAutomationCoverage,
29
28
  validateHumanWritingGovernance,
30
29
  validateInstructionAdapters,
30
+ validateRulesOnlyActiveSurfaceCoverage,
31
31
  validateSkillPurgeSurface,
32
- validateStackResearchEngineCoverage,
32
+ validateStackDecisionBoundaryCoverage,
33
33
  validateTemplateFreeBootstrapCoverage,
34
34
  validateTerminologyMapping,
35
35
  validateUiDesignAutomationCoverage,
@@ -137,6 +137,9 @@ async function validateRequiredFiles() {
137
137
  'scripts/docs-quality-drift-report.mjs',
138
138
  'scripts/governance-weekly-report.mjs',
139
139
  'scripts/mcp-server.mjs',
140
+ 'scripts/mcp-server/constants.mjs',
141
+ 'scripts/mcp-server/tool-registry.mjs',
142
+ 'scripts/mcp-server/tools.mjs',
140
143
  'scripts/frontend-usability-audit.mjs',
141
144
  'scripts/ui-design-judge.mjs',
142
145
  'scripts/documentation-boundary-audit.mjs',
@@ -180,7 +183,7 @@ async function validateRequiredFiles() {
180
183
  'tests/cli-smoke.test.mjs',
181
184
  'tests/mcp-server.test.mjs',
182
185
  'tests/llm-judge.test.mjs',
183
- 'tests/enterprise-ops.test.mjs',
186
+ 'tests/operations.test.mjs',
184
187
  'LICENSE',
185
188
  '.gitignore',
186
189
  ];
@@ -700,7 +703,7 @@ async function main() {
700
703
  await validateDocumentationFlow();
701
704
  await validateTerminologyMapping(coverageValidationContext);
702
705
  await validateDetectionTransparencyCoverage(coverageValidationContext);
703
- await validateStackResearchEngineCoverage(coverageValidationContext);
706
+ await validateStackDecisionBoundaryCoverage(coverageValidationContext);
704
707
  await validateUniversalSopConsolidationCoverage(coverageValidationContext);
705
708
  await validateTemplateFreeBootstrapCoverage(coverageValidationContext);
706
709
  await validateUpgradeUiContractWarningCoverage(coverageValidationContext);
@@ -708,6 +711,7 @@ async function main() {
708
711
  await validateDockerRuntimeAutomationCoverage(coverageValidationContext);
709
712
  await validateDependencyFreshnessAutomationCoverage(coverageValidationContext);
710
713
  await validateDeterministicBoundaryEnforcementCoverage(coverageValidationContext);
714
+ await validateRulesOnlyActiveSurfaceCoverage(coverageValidationContext);
711
715
  await validateStackResearchSnapshotState();
712
716
  await validateMcpConfiguration();
713
717
  await validateHumanWritingGovernance(coverageValidationContext);
@@ -1,431 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import { existsSync, readFileSync } from 'node:fs';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
-
7
- import { BLUEPRINT_RECOMMENDATIONS } from './constants.mjs';
8
- import { ensureDirectory, pathExists, toTitleCase } from './utils.mjs';
9
-
10
- const ARCHITECT_MODULE_FILE_PATH = fileURLToPath(import.meta.url);
11
- const ARCHITECT_PACKAGE_ROOT = path.resolve(path.dirname(ARCHITECT_MODULE_FILE_PATH), '..', '..');
12
-
13
- const ARCHITECT_PREFERENCE_FILE_PATH = process.env.AGENTIC_ARCHITECT_PREF_FILE
14
- ? path.resolve(process.env.AGENTIC_ARCHITECT_PREF_FILE)
15
- : path.join(os.homedir(), '.agentic-senior-core', 'architect-preferences.json');
16
-
17
- // Keyword hints — low-confidence bias only, not authoritative research.
18
- const STACK_SIGNAL_WEIGHTS = {
19
- 'typescript.md': [
20
- { term: 'typescript', weight: 0.9 },
21
- { term: 'javascript', weight: 0.8 },
22
- { term: 'node', weight: 0.55 },
23
- { term: 'next.js', weight: 0.7 },
24
- { term: 'nextjs', weight: 0.7 },
25
- { term: 'react', weight: 0.45 },
26
- { term: 'web app', weight: 0.45 },
27
- { term: 'frontend', weight: 0.4 },
28
- { term: 'dashboard', weight: 0.35 },
29
- ],
30
- 'python.md': [
31
- { term: 'python', weight: 0.95 },
32
- { term: 'fastapi', weight: 0.8 },
33
- { term: 'machine learning', weight: 0.8 },
34
- { term: 'ml', weight: 0.4 },
35
- { term: 'data', weight: 0.45 },
36
- { term: 'ai', weight: 0.45 },
37
- { term: 'automation', weight: 0.35 },
38
- { term: 'analytics', weight: 0.35 },
39
- ],
40
- 'java.md': [
41
- { term: 'java', weight: 0.9 },
42
- { term: 'spring', weight: 0.75 },
43
- { term: 'enterprise', weight: 0.45 },
44
- { term: 'bank', weight: 0.35 },
45
- { term: 'regulated', weight: 0.35 },
46
- { term: 'jvm', weight: 0.35 },
47
- ],
48
- 'php.md': [
49
- { term: 'php', weight: 0.9 },
50
- { term: 'laravel', weight: 0.8 },
51
- { term: 'cms', weight: 0.4 },
52
- { term: 'wordpress', weight: 0.35 },
53
- ],
54
- 'go.md': [
55
- { term: 'go', weight: 0.5 },
56
- { term: 'golang', weight: 0.9 },
57
- { term: 'high throughput', weight: 0.55 },
58
- { term: 'microservice', weight: 0.45 },
59
- { term: 'kubernetes', weight: 0.45 },
60
- { term: 'latency', weight: 0.35 },
61
- { term: 'concurrency', weight: 0.35 },
62
- ],
63
- 'csharp.md': [
64
- { term: '.net', weight: 0.9 },
65
- { term: 'dotnet', weight: 0.9 },
66
- { term: 'c#', weight: 0.75 },
67
- { term: 'asp.net', weight: 0.8 },
68
- { term: 'microsoft', weight: 0.35 },
69
- ],
70
- 'rust.md': [
71
- { term: 'rust', weight: 0.9 },
72
- { term: 'systems', weight: 0.45 },
73
- { term: 'performance', weight: 0.35 },
74
- { term: 'memory safety', weight: 0.4 },
75
- { term: 'low latency', weight: 0.4 },
76
- ],
77
- 'ruby.md': [
78
- { term: 'ruby', weight: 0.9 },
79
- { term: 'rails', weight: 0.8 },
80
- { term: 'monolith', weight: 0.35 },
81
- { term: 'crud', weight: 0.3 },
82
- ],
83
- 'react-native.md': [
84
- { term: 'react native', weight: 0.9 },
85
- { term: 'mobile', weight: 0.4 },
86
- { term: 'android', weight: 0.35 },
87
- { term: 'ios', weight: 0.35 },
88
- { term: 'cross-platform', weight: 0.4 },
89
- ],
90
- 'flutter.md': [
91
- { term: 'flutter', weight: 0.95 },
92
- { term: 'dart', weight: 0.8 },
93
- { term: 'mobile', weight: 0.4 },
94
- { term: 'android', weight: 0.35 },
95
- { term: 'ios', weight: 0.35 },
96
- { term: 'cross-platform', weight: 0.4 },
97
- ],
98
- };
99
-
100
- const STACK_TRADEOFF_SUMMARIES = {
101
- 'typescript.md': 'great fullstack velocity, but dependency churn and runtime startup need discipline.',
102
- 'python.md': 'fast AI and API delivery, but strict latency targets may need extra optimization.',
103
- 'java.md': 'strong enterprise reliability, but setup and boilerplate are heavier.',
104
- 'php.md': 'rapid web product iteration, but architecture boundaries must be guarded early.',
105
- 'go.md': 'excellent throughput and operational simplicity, but abstractions are intentionally minimal.',
106
- 'csharp.md': 'strong for Microsoft-centered teams, but cross-platform tooling choices should be explicit.',
107
- 'rust.md': 'top performance and safety, but development ramp-up is steeper.',
108
- 'ruby.md': 'high productivity for product teams, but scaling strategy should be planned early.',
109
- 'react-native.md': 'single codebase for mobile, but native edge cases still require platform attention.',
110
- 'flutter.md': 'consistent UI across mobile platforms, but ecosystem fit should be checked per package.',
111
- };
112
-
113
- function resolveConfidenceLabel(confidenceScore) {
114
- if (confidenceScore >= 0.85) {
115
- return 'high';
116
- }
117
-
118
- if (confidenceScore >= 0.7) {
119
- return 'medium';
120
- }
121
-
122
- return 'low';
123
- }
124
-
125
- function resolveRecommendedBlueprintFileName(stackFileName, blueprintFileNames) {
126
- const recommendedBlueprintFileName = BLUEPRINT_RECOMMENDATIONS[stackFileName] || null;
127
- if (recommendedBlueprintFileName && blueprintFileNames.includes(recommendedBlueprintFileName)) {
128
- return recommendedBlueprintFileName;
129
- }
130
-
131
- return blueprintFileNames[0] || null;
132
- }
133
-
134
- /**
135
- * Generates a repo-grounded architecture brief based on project description
136
- * keywords and repository marker detection. This is an offline brief —
137
- * for ecosystem-level research, the consuming agent should perform live
138
- * web research rather than relying on stale local snapshots.
139
- */
140
- export function recommendArchitecture({
141
- projectDescription,
142
- projectDetection,
143
- stackFileNames,
144
- blueprintFileNames,
145
- }) {
146
- const normalizedDescription = String(projectDescription || '').trim().toLowerCase();
147
- const effectiveDescription = normalizedDescription || 'general software project';
148
- const uncertaintyNotes = [];
149
-
150
- // Repo marker detection scoring (grounded in actual project files).
151
- const detectionScoreByStackFileName = new Map();
152
- for (const rankedCandidate of projectDetection?.rankedCandidates || []) {
153
- const confidenceScore = Number(rankedCandidate.confidenceScore) || 0;
154
- if (confidenceScore <= 0) {
155
- continue;
156
- }
157
-
158
- detectionScoreByStackFileName.set(
159
- rankedCandidate.stackFileName,
160
- confidenceScore * 1.75
161
- );
162
- }
163
-
164
- // Keyword hint scoring (low-confidence bias, not research).
165
- const scoredStackCandidates = stackFileNames.map((stackFileName) => {
166
- const configuredSignals = STACK_SIGNAL_WEIGHTS[stackFileName] || [];
167
- const matchedKeywords = [];
168
- let keywordScore = 0;
169
-
170
- for (const configuredSignal of configuredSignals) {
171
- if (!effectiveDescription.includes(configuredSignal.term)) {
172
- continue;
173
- }
174
-
175
- keywordScore += configuredSignal.weight;
176
- matchedKeywords.push(configuredSignal.term);
177
- }
178
-
179
- const detectionScore = detectionScoreByStackFileName.get(stackFileName) || 0;
180
- const totalScore = 0.2 + keywordScore + detectionScore;
181
-
182
- return {
183
- stackFileName,
184
- totalScore,
185
- keywordScore,
186
- detectionScore,
187
- matchedKeywords,
188
- };
189
- }).sort((leftCandidate, rightCandidate) => rightCandidate.totalScore - leftCandidate.totalScore);
190
-
191
- // Fallback when no candidates can be scored.
192
- if (scoredStackCandidates.length === 0) {
193
- const fallbackStackFileName = stackFileNames.includes('typescript.md')
194
- ? 'typescript.md'
195
- : stackFileNames[0] || 'typescript.md';
196
- const fallbackBlueprintFileName = resolveRecommendedBlueprintFileName(fallbackStackFileName, blueprintFileNames);
197
-
198
- return {
199
- briefType: 'offline',
200
- projectDescription: String(projectDescription || '').trim(),
201
- recommendedStackFileName: fallbackStackFileName,
202
- recommendedBlueprintFileName: fallbackBlueprintFileName,
203
- confidenceLabel: 'low',
204
- confidenceScore: 0.35,
205
- rationaleSentences: [
206
- `Defaulting to ${toTitleCase(fallbackStackFileName)} with ${toTitleCase(fallbackBlueprintFileName)} as a safe fallback.`,
207
- 'No keyword or detection signals were strong enough for a grounded recommendation.',
208
- 'For ecosystem-level validation, perform live web research before committing to this stack.',
209
- ],
210
- alternatives: [],
211
- uncertaintyNotes: [
212
- 'This is an offline brief based on keyword hints and repo markers only.',
213
- 'Ecosystem fitness was not verified — the agent should research before finalizing.',
214
- ],
215
- signalSummary: 'fallback (no strong signals)',
216
- failureModes: {
217
- lowConfidence: true,
218
- dataConflict: false,
219
- repeatedOverride: false,
220
- },
221
- };
222
- }
223
-
224
- const strongestCandidate = scoredStackCandidates[0];
225
- const secondCandidate = scoredStackCandidates[1] || null;
226
- const scoreGap = secondCandidate
227
- ? strongestCandidate.totalScore - secondCandidate.totalScore
228
- : strongestCandidate.totalScore;
229
-
230
- let confidenceScore = 0.55
231
- + Math.min(strongestCandidate.totalScore / 8, 0.25)
232
- + Math.min(Math.max(scoreGap, 0) / 3, 0.18);
233
-
234
- if (strongestCandidate.matchedKeywords.length === 0) {
235
- confidenceScore -= 0.2;
236
- }
237
-
238
- if (secondCandidate && scoreGap < 0.18) {
239
- confidenceScore -= 0.1;
240
- }
241
-
242
- confidenceScore = Math.min(Math.max(confidenceScore, 0.35), 0.97);
243
-
244
- const confidenceLabel = resolveConfidenceLabel(confidenceScore);
245
- const lowConfidence = confidenceScore < 0.7;
246
- const dataConflict = Boolean(secondCandidate && scoreGap < 0.18);
247
-
248
- if (lowConfidence) {
249
- uncertaintyNotes.push('Low confidence: description did not map strongly to a single stack profile.');
250
- }
251
-
252
- if (dataConflict) {
253
- uncertaintyNotes.push('Data conflict: top stack candidates are close, so trade-offs need manual confirmation.');
254
- }
255
-
256
- // Offline briefs should always be transparent about their grounding source.
257
- uncertaintyNotes.push(
258
- 'This brief is grounded in repo markers and keyword hints only. '
259
- + 'For ecosystem-level validation, the agent should perform live web research.'
260
- );
261
-
262
- const recommendedStackFileName = strongestCandidate.stackFileName;
263
- const recommendedBlueprintFileName = resolveRecommendedBlueprintFileName(recommendedStackFileName, blueprintFileNames);
264
-
265
- const signalSummaryParts = [];
266
- if (strongestCandidate.matchedKeywords.length > 0) {
267
- signalSummaryParts.push(`keywords: ${strongestCandidate.matchedKeywords.slice(0, 4).join(', ')}`);
268
- }
269
- if (strongestCandidate.detectionScore > 0) {
270
- signalSummaryParts.push(`repo detection: ${strongestCandidate.detectionScore.toFixed(2)}`);
271
- }
272
- const signalSummary = signalSummaryParts.length > 0
273
- ? signalSummaryParts.join('; ')
274
- : 'weak signals only';
275
-
276
- const rationaleSentences = [
277
- `Stack brief: ${toTitleCase(recommendedStackFileName)} with ${toTitleCase(recommendedBlueprintFileName)}.`,
278
- `Grounding signals: ${signalSummary}.`,
279
- `Main trade-off: ${STACK_TRADEOFF_SUMMARIES[recommendedStackFileName] || 'validate ecosystem fit before production rollout.'}`,
280
- ];
281
-
282
- if (lowConfidence) {
283
- rationaleSentences.push('Confidence is low — review alternatives and perform live research before finalizing.');
284
- }
285
-
286
- const alternatives = scoredStackCandidates
287
- .slice(1, 3)
288
- .map((stackCandidate) => {
289
- const alternativeBlueprintFileName = resolveRecommendedBlueprintFileName(stackCandidate.stackFileName, blueprintFileNames);
290
- return {
291
- stackFileName: stackCandidate.stackFileName,
292
- blueprintFileName: alternativeBlueprintFileName,
293
- oneLineTradeoff: STACK_TRADEOFF_SUMMARIES[stackCandidate.stackFileName]
294
- || 'validate fit with your runtime and team constraints.',
295
- evidenceSummary: `keyword=${stackCandidate.keywordScore.toFixed(2)}, detection=${stackCandidate.detectionScore.toFixed(2)}`,
296
- };
297
- });
298
-
299
- return {
300
- briefType: 'offline',
301
- projectDescription: String(projectDescription || '').trim(),
302
- recommendedStackFileName,
303
- recommendedBlueprintFileName,
304
- confidenceLabel,
305
- confidenceScore: Number(confidenceScore.toFixed(2)),
306
- rationaleSentences,
307
- alternatives,
308
- uncertaintyNotes,
309
- signalSummary,
310
- failureModes: {
311
- lowConfidence,
312
- dataConflict,
313
- repeatedOverride: false,
314
- },
315
- };
316
- }
317
-
318
- export function formatArchitectureRecommendation(architectureRecommendation) {
319
- const outputLines = [
320
- '\nArchitecture brief (offline, repo-grounded):',
321
- `- Stack: ${toTitleCase(architectureRecommendation.recommendedStackFileName)}`,
322
- `- Blueprint: ${toTitleCase(architectureRecommendation.recommendedBlueprintFileName)}`,
323
- `- Confidence: ${architectureRecommendation.confidenceLabel} (${architectureRecommendation.confidenceScore})`,
324
- '- Rationale:',
325
- ...architectureRecommendation.rationaleSentences.map((sentence, sentenceIndex) => ` ${sentenceIndex + 1}. ${sentence}`),
326
- '- Alternatives:',
327
- ];
328
-
329
- if (architectureRecommendation.alternatives.length === 0) {
330
- outputLines.push(' 1. No strong alternatives detected from current input signals.');
331
- } else {
332
- outputLines.push(
333
- ...architectureRecommendation.alternatives.map(
334
- (alternative, alternativeIndex) => ` ${alternativeIndex + 1}. ${toTitleCase(alternative.stackFileName)} + ${toTitleCase(alternative.blueprintFileName)}: ${alternative.oneLineTradeoff}`
335
- )
336
- );
337
- }
338
-
339
- if (architectureRecommendation.uncertaintyNotes.length > 0) {
340
- outputLines.push('- Uncertainty notes:');
341
- outputLines.push(
342
- ...architectureRecommendation.uncertaintyNotes.map(
343
- (uncertaintyNote, uncertaintyIndex) => ` ${uncertaintyIndex + 1}. ${uncertaintyNote}`
344
- )
345
- );
346
- }
347
-
348
- const cautionLabels = [];
349
- if (architectureRecommendation.failureModes.lowConfidence) {
350
- cautionLabels.push('low-confidence');
351
- }
352
- if (architectureRecommendation.failureModes.dataConflict) {
353
- cautionLabels.push('data-conflict');
354
- }
355
- if (architectureRecommendation.failureModes.repeatedOverride) {
356
- cautionLabels.push('repeated-override');
357
- }
358
-
359
- if (cautionLabels.length > 0) {
360
- outputLines.push(`- Caution labels: ${cautionLabels.join(', ')}`);
361
- }
362
-
363
- return outputLines.join('\n');
364
- }
365
-
366
- export async function readArchitectPreferenceState() {
367
- if (!(await pathExists(ARCHITECT_PREFERENCE_FILE_PATH))) {
368
- return null;
369
- }
370
-
371
- try {
372
- const rawPreferenceContent = await fs.readFile(ARCHITECT_PREFERENCE_FILE_PATH, 'utf8');
373
- const parsedPreferencePayload = JSON.parse(rawPreferenceContent);
374
- const parsedPreferenceState = parsedPreferencePayload?.preference;
375
-
376
- if (!parsedPreferenceState?.preferredStackFileName) {
377
- return null;
378
- }
379
-
380
- return {
381
- preferredStackFileName: parsedPreferenceState.preferredStackFileName,
382
- preferredBlueprintFileName: parsedPreferenceState.preferredBlueprintFileName || null,
383
- overrideCount: Number(parsedPreferenceState.overrideCount) || 0,
384
- lastOverrideAt: parsedPreferenceState.lastOverrideAt || null,
385
- };
386
- } catch {
387
- return null;
388
- }
389
- }
390
-
391
- export function createUpdatedArchitectPreference(currentPreferenceState, {
392
- selectedStackFileName,
393
- selectedBlueprintFileName,
394
- }) {
395
- const currentOverrideCount = Number(currentPreferenceState?.overrideCount) || 0;
396
-
397
- return {
398
- preferredStackFileName: selectedStackFileName,
399
- preferredBlueprintFileName: selectedBlueprintFileName,
400
- overrideCount: currentOverrideCount + 1,
401
- lastOverrideAt: new Date().toISOString(),
402
- };
403
- }
404
-
405
- export function shouldApplyRepeatedOverridePreference(preferenceState, recommendedStackFileName) {
406
- if (!preferenceState?.preferredStackFileName) {
407
- return false;
408
- }
409
-
410
- if ((Number(preferenceState.overrideCount) || 0) < 2) {
411
- return false;
412
- }
413
-
414
- return preferenceState.preferredStackFileName !== recommendedStackFileName;
415
- }
416
-
417
- export async function writeArchitectPreferenceState(preferenceState) {
418
- const preferencePayload = {
419
- schemaVersion: '1.0.0',
420
- updatedAt: new Date().toISOString(),
421
- preference: {
422
- preferredStackFileName: preferenceState.preferredStackFileName,
423
- preferredBlueprintFileName: preferenceState.preferredBlueprintFileName,
424
- overrideCount: preferenceState.overrideCount,
425
- lastOverrideAt: preferenceState.lastOverrideAt,
426
- },
427
- };
428
-
429
- await ensureDirectory(path.dirname(ARCHITECT_PREFERENCE_FILE_PATH));
430
- await fs.writeFile(ARCHITECT_PREFERENCE_FILE_PATH, JSON.stringify(preferencePayload, null, 2) + '\n', 'utf8');
431
- }