@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.
- package/.agent-context/prompts/bootstrap-design.md +84 -103
- package/.agent-context/prompts/init-project.md +32 -100
- package/.agent-context/prompts/refactor.md +22 -44
- package/.agent-context/prompts/review-code.md +28 -52
- package/.agent-context/review-checklists/architecture-review.md +31 -62
- package/.agent-context/review-checklists/pr-checklist.md +74 -108
- package/.agent-context/rules/api-docs.md +18 -206
- package/.agent-context/rules/architecture.md +40 -207
- package/.agent-context/rules/database-design.md +10 -199
- package/.agent-context/rules/docker-runtime.md +5 -5
- package/.agent-context/rules/efficiency-vs-hype.md +11 -149
- package/.agent-context/rules/error-handling.md +9 -231
- package/.agent-context/rules/event-driven.md +17 -221
- package/.agent-context/rules/frontend-architecture.md +66 -119
- package/.agent-context/rules/git-workflow.md +1 -1
- package/.agent-context/rules/microservices.md +28 -161
- package/.agent-context/rules/naming-conv.md +8 -138
- package/.agent-context/rules/performance.md +9 -175
- package/.agent-context/rules/realtime.md +11 -44
- package/.agent-context/rules/security.md +11 -295
- package/.agent-context/rules/testing.md +9 -174
- package/.agent-context/state/benchmark-analysis.json +3 -3
- package/.agent-context/state/memory-continuity-benchmark.json +1 -1
- package/.agent-context/state/onboarding-report.json +71 -11
- package/.agents/workflows/init-project.md +7 -24
- package/.agents/workflows/refactor.md +7 -24
- package/.agents/workflows/review-code.md +7 -24
- package/.cursorrules +22 -21
- package/.gemini/instructions.md +2 -2
- package/.github/copilot-instructions.md +2 -2
- package/.instructions.md +112 -213
- package/.windsurfrules +22 -21
- package/AGENTS.md +4 -4
- package/CONTRIBUTING.md +13 -22
- package/README.md +6 -20
- package/lib/cli/commands/init.mjs +102 -148
- package/lib/cli/commands/launch.mjs +3 -3
- package/lib/cli/commands/optimize.mjs +14 -4
- package/lib/cli/commands/upgrade.mjs +25 -23
- package/lib/cli/compiler.mjs +96 -62
- package/lib/cli/constants.mjs +28 -136
- package/lib/cli/detector/design-evidence.mjs +189 -6
- package/lib/cli/detector.mjs +6 -7
- package/lib/cli/init-detection-flow.mjs +10 -93
- package/lib/cli/init-selection.mjs +2 -68
- package/lib/cli/project-scaffolder/constants.mjs +1 -1
- package/lib/cli/project-scaffolder/design-contract.mjs +162 -108
- package/lib/cli/project-scaffolder/discovery.mjs +36 -82
- package/lib/cli/project-scaffolder/prompt-builders.mjs +41 -55
- package/lib/cli/project-scaffolder/storage.mjs +0 -2
- package/lib/cli/token-optimization.mjs +1 -1
- package/lib/cli/utils.mjs +75 -9
- package/package.json +2 -2
- package/scripts/detection-benchmark.mjs +4 -15
- package/scripts/documentation-boundary-audit.mjs +9 -9
- package/scripts/explain-on-demand-audit.mjs +11 -11
- package/scripts/forbidden-content-check.mjs +9 -9
- package/scripts/frontend-usability-audit.mjs +45 -35
- package/scripts/llm-judge.mjs +1 -1
- package/scripts/mcp-server/constants.mjs +2 -2
- package/scripts/mcp-server/tool-registry.mjs +1 -1
- package/scripts/release-gate/audit-checks.mjs +4 -4
- package/scripts/release-gate/static-checks.mjs +5 -5
- package/scripts/release-gate.mjs +1 -1
- package/scripts/rules-guardian-audit.mjs +14 -13
- package/scripts/single-source-lazy-loading-audit.mjs +3 -3
- package/scripts/sync-thin-adapters.mjs +5 -5
- package/scripts/ui-design-judge/design-execution-summary.mjs +27 -1
- package/scripts/ui-design-judge/prompting.mjs +4 -4
- package/scripts/ui-design-judge/reporting.mjs +2 -1
- package/scripts/ui-design-judge/rubric-calibration.mjs +8 -5
- package/scripts/ui-design-judge/rubric-goldset.json +2 -2
- package/scripts/ui-design-judge.mjs +70 -6
- package/scripts/validate/config.mjs +138 -48
- package/scripts/validate/coverage-checks.mjs +32 -7
- package/scripts/validate.mjs +8 -4
- 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
|
-
|
|
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('
|
|
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
|
|
195
|
+
export async function validateStackDecisionBoundaryCoverage(context) {
|
|
195
196
|
await validateSnippetCoverage({
|
|
196
|
-
heading: 'Checking stack
|
|
197
|
-
coverageRules:
|
|
198
|
-
missingLabel: 'stack
|
|
199
|
-
snippetLabel: 'stack
|
|
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
|
|
package/scripts/validate.mjs
CHANGED
|
@@ -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
|
-
|
|
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/
|
|
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
|
|
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);
|
package/lib/cli/architect.mjs
DELETED
|
@@ -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
|
-
}
|