@ryuenn3123/agentic-senior-core 3.0.7 → 3.0.8
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/state/memory-continuity-benchmark.json +1 -1
- package/.cursorrules +1 -1
- package/.windsurfrules +1 -1
- package/README.md +5 -5
- package/lib/cli/commands/init.mjs +83 -1
- package/lib/cli/commands/upgrade.mjs +66 -95
- package/lib/cli/detector.mjs +101 -0
- package/lib/cli/project-scaffolder.mjs +34 -8
- package/package.json +1 -1
- package/scripts/validate.mjs +4 -2
package/.cursorrules
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AGENTIC-SENIOR-CORE DYNAMIC GOVERNANCE RULESET
|
|
2
2
|
|
|
3
|
-
Generated by Agentic-Senior-Core CLI v3.0.
|
|
3
|
+
Generated by Agentic-Senior-Core CLI v3.0.8
|
|
4
4
|
Timestamp: 2026-04-18T00:00:00.000Z
|
|
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 v3.0.
|
|
3
|
+
Generated by Agentic-Senior-Core CLI v3.0.8
|
|
4
4
|
Timestamp: 2026-04-18T00:00:00.000Z
|
|
5
5
|
Selected profile: beginner
|
|
6
6
|
Selected policy file: .agent-context/policies/llm-judge-threshold.json
|
package/README.md
CHANGED
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
**Production-grade Rules Engine (Governance Engine) for AI coding agents.**
|
|
11
11
|
Works with Cursor, Windsurf, GitHub Copilot, Claude Code, Gemini, and other LLM-powered IDE workflows.
|
|
12
12
|
|
|
13
|
-
Latest release: 3.0.
|
|
13
|
+
Latest release: 3.0.8 (2026-04-20).
|
|
14
14
|
|
|
15
|
-
Highlights in 3.0.
|
|
16
|
-
-
|
|
17
|
-
- Upgrade
|
|
18
|
-
-
|
|
15
|
+
Highlights in 3.0.8:
|
|
16
|
+
- Existing UI repositories now get `docs/design-intent.json` seeded during both `init` and `upgrade`.
|
|
17
|
+
- Upgrade preview explicitly shows when a machine-readable design seed will be created on apply.
|
|
18
|
+
- UI scope detection is now shared between `init` and `upgrade` so design-contract enforcement stays consistent.
|
|
19
19
|
|
|
20
20
|
</div>
|
|
21
21
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { createInterface } from 'node:readline/promises';
|
|
6
6
|
import { stdin, stdout } from 'node:process';
|
|
7
|
+
import fs from 'node:fs/promises';
|
|
7
8
|
import path from 'node:path';
|
|
8
9
|
|
|
9
10
|
import {
|
|
@@ -29,10 +30,16 @@ import {
|
|
|
29
30
|
formatBlockingSeverities,
|
|
30
31
|
formatDuration,
|
|
31
32
|
copyGovernanceAssetsToTarget,
|
|
33
|
+
pathExists,
|
|
32
34
|
} from '../utils.mjs';
|
|
33
35
|
|
|
34
36
|
import { collectProfilePacks, findProfilePackByInput } from '../profile-packs.mjs';
|
|
35
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
detectProjectContext,
|
|
39
|
+
buildDetectionSummary,
|
|
40
|
+
formatDetectionCandidates,
|
|
41
|
+
detectUiScopeSignals,
|
|
42
|
+
} from '../detector.mjs';
|
|
36
43
|
import { compileDynamicContext, writeSelectedPolicy, writeOnboardingReport } from '../compiler.mjs';
|
|
37
44
|
import {
|
|
38
45
|
filterBlueprintFileNamesByCandidates,
|
|
@@ -55,6 +62,7 @@ import {
|
|
|
55
62
|
hasExistingProjectDocs,
|
|
56
63
|
loadProjectConfig,
|
|
57
64
|
normalizeDocsLanguage,
|
|
65
|
+
buildDesignIntentSeedFromSignals,
|
|
58
66
|
} from '../project-scaffolder.mjs';
|
|
59
67
|
import { performRollback } from '../rollback.mjs';
|
|
60
68
|
import {
|
|
@@ -162,6 +170,41 @@ async function askBlueprintSelection(promptMessage, selectableBlueprintFileNames
|
|
|
162
170
|
return selectableBlueprintFileNames[selectedIndex] || selectableBlueprintFileNames[0] || null;
|
|
163
171
|
}
|
|
164
172
|
|
|
173
|
+
function buildInitExistingProjectDesignIntentSeed({
|
|
174
|
+
targetDirectoryPath,
|
|
175
|
+
packageManifest,
|
|
176
|
+
selectedStackFileName,
|
|
177
|
+
selectedBlueprintFileName,
|
|
178
|
+
uiScopeSignals,
|
|
179
|
+
architectureProjectDescription,
|
|
180
|
+
}) {
|
|
181
|
+
const projectName = String(packageManifest?.name || path.basename(targetDirectoryPath)).trim() || 'existing-ui-project';
|
|
182
|
+
const isMobileUiProject = String(selectedStackFileName || '').toLowerCase().includes('react-native')
|
|
183
|
+
|| String(selectedStackFileName || '').toLowerCase().includes('flutter')
|
|
184
|
+
|| uiScopeSignals.signalReasons.some((signalReason) => signalReason.includes('android') || signalReason.includes('ios'));
|
|
185
|
+
const resolvedDomain = isMobileUiProject ? 'Mobile app' : 'Web application';
|
|
186
|
+
const projectDescription = String(packageManifest?.description || architectureProjectDescription || '').trim()
|
|
187
|
+
|| `Existing ${resolvedDomain.toLowerCase()} detected during init. Create a project-specific dynamic design contract before shipping new UI work.`;
|
|
188
|
+
|
|
189
|
+
return buildDesignIntentSeedFromSignals({
|
|
190
|
+
projectName,
|
|
191
|
+
projectDescription,
|
|
192
|
+
primaryDomain: resolvedDomain,
|
|
193
|
+
features: [],
|
|
194
|
+
initContext: {
|
|
195
|
+
stackFileName: selectedStackFileName,
|
|
196
|
+
blueprintFileName: selectedBlueprintFileName,
|
|
197
|
+
},
|
|
198
|
+
status: 'seed-generated-during-init',
|
|
199
|
+
supplementalFields: {
|
|
200
|
+
initSignals: {
|
|
201
|
+
detectedFrom: uiScopeSignals.signalReasons,
|
|
202
|
+
generatedBy: 'init-existing-project-seed',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
165
208
|
export async function runInitCommand(targetDirectoryArgument, initOptions = {}) {
|
|
166
209
|
const resolvedTargetDirectoryPath = path.resolve(targetDirectoryArgument || '.');
|
|
167
210
|
const isTokenOptimizationEnabled = typeof initOptions.tokenOptimize === 'boolean'
|
|
@@ -580,6 +623,7 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
580
623
|
|
|
581
624
|
// --- Project Documentation Scaffolding ---
|
|
582
625
|
let scaffoldingResult = null;
|
|
626
|
+
const supplementalMaterializedDocFileNames = [];
|
|
583
627
|
const isFreshProjectTarget = wasDirectoryEffectivelyEmpty && !hadExistingProjectDocsBeforeInit;
|
|
584
628
|
const shouldOfferScaffolding = initOptions.scaffoldDocs === true
|
|
585
629
|
|| Boolean(initOptions.projectConfig)
|
|
@@ -661,6 +705,36 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
661
705
|
}
|
|
662
706
|
}
|
|
663
707
|
|
|
708
|
+
const existingProjectUiScopeSignals = projectDetection.hasExistingProjectFiles
|
|
709
|
+
? await detectUiScopeSignals({
|
|
710
|
+
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
711
|
+
selectedStackFileName: selectedResolvedStackFileName,
|
|
712
|
+
selectedBlueprintFileName: selectedResolvedBlueprintFileName,
|
|
713
|
+
})
|
|
714
|
+
: null;
|
|
715
|
+
const designIntentTargetPath = path.join(resolvedTargetDirectoryPath, 'docs', 'design-intent.json');
|
|
716
|
+
const shouldSeedExistingUiDesignIntent = projectDetection.hasExistingProjectFiles
|
|
717
|
+
&& existingProjectUiScopeSignals?.isUiScopeLikely === true
|
|
718
|
+
&& !(await pathExists(designIntentTargetPath));
|
|
719
|
+
|
|
720
|
+
if (shouldSeedExistingUiDesignIntent) {
|
|
721
|
+
const docsDirectoryPath = path.join(resolvedTargetDirectoryPath, 'docs');
|
|
722
|
+
const designIntentSeedContent = buildInitExistingProjectDesignIntentSeed({
|
|
723
|
+
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
724
|
+
packageManifest: existingProjectUiScopeSignals.packageManifest,
|
|
725
|
+
selectedStackFileName: selectedResolvedStackFileName,
|
|
726
|
+
selectedBlueprintFileName: selectedResolvedBlueprintFileName,
|
|
727
|
+
uiScopeSignals: existingProjectUiScopeSignals,
|
|
728
|
+
architectureProjectDescription,
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
await ensureDirectory(docsDirectoryPath);
|
|
732
|
+
await fs.writeFile(designIntentTargetPath, designIntentSeedContent, 'utf8');
|
|
733
|
+
supplementalMaterializedDocFileNames.push('design-intent.json');
|
|
734
|
+
|
|
735
|
+
console.log('\nExisting UI/frontend scope detected. Seeded docs/design-intent.json so the machine-readable design contract exists before UI implementation work continues.');
|
|
736
|
+
}
|
|
737
|
+
|
|
664
738
|
await compileDynamicContext({
|
|
665
739
|
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
666
740
|
selectedProfileName,
|
|
@@ -765,6 +839,9 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
765
839
|
console.log(`- Project docs: ${scaffoldingResult.generatedFileNames.length} files generated in docs/`);
|
|
766
840
|
console.log(`- Project docs language: ${scaffoldingResult.docsLanguage}`);
|
|
767
841
|
}
|
|
842
|
+
if (supplementalMaterializedDocFileNames.length > 0) {
|
|
843
|
+
console.log(`- Design seed docs: ${supplementalMaterializedDocFileNames.length} files generated in docs/`);
|
|
844
|
+
}
|
|
768
845
|
console.log(`- Repository workflows copied: no (workflows remain source-repo assets)`);
|
|
769
846
|
console.log(`- MCP configuration: ${shouldIncludeMcpTemplate ? 'auto-configured for your IDEs (VS Code, Cursor, Zed, Gemini)' : 'disabled (--no-mcp-template)'}`);
|
|
770
847
|
if (isMemoryContinuityEnabled) {
|
|
@@ -804,6 +881,11 @@ export async function runInitCommand(targetDirectoryArgument, initOptions = {})
|
|
|
804
881
|
console.log(`- Build an MVP for ${promptProjectName}. Follow Layer 9 docs and keep the current stack, database, and auth constraints.`);
|
|
805
882
|
console.log('- Add [new feature] and update docs/project-brief.md plus docs/flow-overview.md in the same change.');
|
|
806
883
|
console.log('- If this change needs architecture migration, propose a migration plan first, then implement after approval.');
|
|
884
|
+
} else if (supplementalMaterializedDocFileNames.includes('design-intent.json')) {
|
|
885
|
+
console.log('I also seeded docs/design-intent.json for this existing UI repository so future UI work starts from a machine-readable design contract without forcing a canned visual concept.');
|
|
886
|
+
console.log('\nPrompt starter examples (copy and adapt in your IDE):');
|
|
887
|
+
console.log('- If docs/DESIGN.md is missing, execute .agent-context/prompts/bootstrap-design.md now and refine docs/design-intent.json into a complete design contract before building UI components.');
|
|
888
|
+
console.log('- Keep docs/design-intent.json and docs/DESIGN.md synchronized whenever the UI direction changes.');
|
|
807
889
|
}
|
|
808
890
|
console.log('Your AI tools will now receive one compiled rulebook plus the original source rules, and your review threshold is stored in .agent-context/policies/llm-judge-threshold.json.');
|
|
809
891
|
console.log('MCP server registration is manual inside your IDE settings, even when mcp.json exists.');
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
detectProjectContext,
|
|
30
30
|
buildDetectionSummary,
|
|
31
31
|
formatDetectionCandidates,
|
|
32
|
-
|
|
32
|
+
detectUiScopeSignals,
|
|
33
33
|
} from '../detector.mjs';
|
|
34
34
|
import {
|
|
35
35
|
buildCompiledRulesContent,
|
|
@@ -41,7 +41,10 @@ import {
|
|
|
41
41
|
import { runPreflightChecks } from '../preflight.mjs';
|
|
42
42
|
import { createBackup } from '../backup.mjs';
|
|
43
43
|
import { performRollback } from '../rollback.mjs';
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
detectProjectDocTemplateStaleness,
|
|
46
|
+
buildDesignIntentSeedFromSignals,
|
|
47
|
+
} from '../project-scaffolder.mjs';
|
|
45
48
|
|
|
46
49
|
export function parseUpgradeArguments(commandArguments) {
|
|
47
50
|
const parsedUpgradeOptions = {
|
|
@@ -104,100 +107,38 @@ function buildExistingProjectMajorConstraints() {
|
|
|
104
107
|
];
|
|
105
108
|
}
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
const packageJsonPath = path.join(targetDirectoryPath, 'package.json');
|
|
109
|
-
if (!(await pathExists(packageJsonPath))) {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
return JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
115
|
-
} catch {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function detectUiScopeSignals({
|
|
110
|
+
function buildUpgradeDesignIntentSeed({
|
|
121
111
|
targetDirectoryPath,
|
|
122
|
-
|
|
112
|
+
packageManifest,
|
|
123
113
|
selectedStackFileName,
|
|
124
114
|
selectedBlueprintFileName,
|
|
115
|
+
uiScopeSignals,
|
|
125
116
|
}) {
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
'tailwind.config.ts',
|
|
152
|
-
'vite.config.js',
|
|
153
|
-
'vite.config.mjs',
|
|
154
|
-
'vite.config.ts',
|
|
155
|
-
'react-native.config.js',
|
|
156
|
-
'app',
|
|
157
|
-
'pages',
|
|
158
|
-
'components',
|
|
159
|
-
'public',
|
|
160
|
-
'styles',
|
|
161
|
-
'android',
|
|
162
|
-
'ios',
|
|
163
|
-
'index.html',
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
const detectedUiMarkers = directUiMarkerNames.filter((markerName) => markerNames.has(markerName));
|
|
167
|
-
if (detectedUiMarkers.length > 0) {
|
|
168
|
-
signalReasons.push(`ui markers: ${detectedUiMarkers.join(', ')}`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const dependencyMap = {
|
|
172
|
-
next: 'next',
|
|
173
|
-
react: 'react',
|
|
174
|
-
reactDom: 'react-dom',
|
|
175
|
-
reactNative: 'react-native',
|
|
176
|
-
expo: 'expo',
|
|
177
|
-
tailwindcss: 'tailwindcss',
|
|
178
|
-
};
|
|
179
|
-
const dependencySource = {
|
|
180
|
-
...(packageManifest?.dependencies || {}),
|
|
181
|
-
...(packageManifest?.devDependencies || {}),
|
|
182
|
-
};
|
|
183
|
-
const detectedUiDependencies = Object.values(dependencyMap).filter((dependencyName) => dependencySource[dependencyName]);
|
|
184
|
-
if (detectedUiDependencies.length > 0) {
|
|
185
|
-
signalReasons.push(`ui dependencies: ${detectedUiDependencies.join(', ')}`);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const hasStrongUiMarker = detectedUiMarkers.some((markerName) => (
|
|
189
|
-
markerName.startsWith('next.config')
|
|
190
|
-
|| markerName === 'react-native.config.js'
|
|
191
|
-
|| markerName === 'android'
|
|
192
|
-
|| markerName === 'ios'
|
|
193
|
-
));
|
|
194
|
-
const hasUiDependencies = detectedUiDependencies.length > 0;
|
|
195
|
-
const hasStructuralUiMarkers = detectedUiMarkers.length >= 2;
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
isUiScopeLikely: signalReasons.length > 0 && (hasStrongUiMarker || hasUiDependencies || hasStructuralUiMarkers || persistedProjectScopeKey.length > 0),
|
|
199
|
-
signalReasons,
|
|
200
|
-
};
|
|
117
|
+
const projectName = String(packageManifest?.name || path.basename(targetDirectoryPath)).trim() || 'existing-ui-project';
|
|
118
|
+
const isMobileUiProject = String(selectedStackFileName || '').toLowerCase().includes('react-native')
|
|
119
|
+
|| String(selectedStackFileName || '').toLowerCase().includes('flutter')
|
|
120
|
+
|| uiScopeSignals.signalReasons.some((signalReason) => signalReason.includes('android') || signalReason.includes('ios'));
|
|
121
|
+
const resolvedDomain = isMobileUiProject ? 'Mobile app' : 'Web application';
|
|
122
|
+
const projectDescription = String(packageManifest?.description || '').trim()
|
|
123
|
+
|| `Existing ${resolvedDomain.toLowerCase()} detected during upgrade. Create a project-specific dynamic design contract before shipping new UI work.`;
|
|
124
|
+
|
|
125
|
+
return buildDesignIntentSeedFromSignals({
|
|
126
|
+
projectName,
|
|
127
|
+
projectDescription,
|
|
128
|
+
primaryDomain: resolvedDomain,
|
|
129
|
+
features: [],
|
|
130
|
+
initContext: {
|
|
131
|
+
stackFileName: selectedStackFileName,
|
|
132
|
+
blueprintFileName: selectedBlueprintFileName,
|
|
133
|
+
},
|
|
134
|
+
status: 'seed-generated-during-upgrade',
|
|
135
|
+
supplementalFields: {
|
|
136
|
+
upgradeSignals: {
|
|
137
|
+
detectedFrom: uiScopeSignals.signalReasons,
|
|
138
|
+
generatedBy: 'upgrade-seed',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
});
|
|
201
142
|
}
|
|
202
143
|
|
|
203
144
|
export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions = {}) {
|
|
@@ -232,7 +173,6 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
232
173
|
const blueprintFileNames = await collectFileNames(path.join(AGENT_CONTEXT_DIR, 'blueprints'));
|
|
233
174
|
const existingOnboardingReport = await loadOnboardingReportIfExists(resolvedTargetDirectoryPath);
|
|
234
175
|
const projectDetection = await detectProjectContext(resolvedTargetDirectoryPath);
|
|
235
|
-
|
|
236
176
|
const selectedProfileName = PROFILE_PRESETS[existingOnboardingReport?.selectedProfile]
|
|
237
177
|
? existingOnboardingReport.selectedProfile
|
|
238
178
|
: 'balanced';
|
|
@@ -266,10 +206,12 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
266
206
|
: true;
|
|
267
207
|
const uiScopeSignals = await detectUiScopeSignals({
|
|
268
208
|
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
269
|
-
existingOnboardingReport,
|
|
270
209
|
selectedStackFileName,
|
|
271
210
|
selectedBlueprintFileName,
|
|
211
|
+
projectScopeKey: existingOnboardingReport?.projectScope?.key || null,
|
|
212
|
+
projectScopeSourceLabel: 'onboarding project scope',
|
|
272
213
|
});
|
|
214
|
+
const packageManifest = uiScopeSignals.packageManifest;
|
|
273
215
|
const designContractPaths = ['docs/DESIGN.md', 'docs/design-intent.json'];
|
|
274
216
|
const missingDesignContractPaths = [];
|
|
275
217
|
|
|
@@ -279,6 +221,17 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
279
221
|
missingDesignContractPaths.push(designContractPath);
|
|
280
222
|
}
|
|
281
223
|
}
|
|
224
|
+
const shouldSeedDesignIntentOnApply = uiScopeSignals.isUiScopeLikely
|
|
225
|
+
&& missingDesignContractPaths.includes('docs/design-intent.json');
|
|
226
|
+
const designIntentSeedContent = shouldSeedDesignIntentOnApply
|
|
227
|
+
? buildUpgradeDesignIntentSeed({
|
|
228
|
+
targetDirectoryPath: resolvedTargetDirectoryPath,
|
|
229
|
+
packageManifest,
|
|
230
|
+
selectedStackFileName,
|
|
231
|
+
selectedBlueprintFileName,
|
|
232
|
+
uiScopeSignals,
|
|
233
|
+
})
|
|
234
|
+
: null;
|
|
282
235
|
|
|
283
236
|
const detectionMajorConstraints = buildExistingProjectMajorConstraints();
|
|
284
237
|
const detectionTransparency = {
|
|
@@ -408,8 +361,11 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
408
361
|
if (uiScopeSignals.signalReasons.length > 0) {
|
|
409
362
|
console.log(`- Detection signals: ${uiScopeSignals.signalReasons.join('; ')}`);
|
|
410
363
|
}
|
|
364
|
+
if (shouldSeedDesignIntentOnApply) {
|
|
365
|
+
console.log('- Planned seed on apply: docs/design-intent.json');
|
|
366
|
+
}
|
|
411
367
|
console.log('Recommendation: create or refresh docs/DESIGN.md and docs/design-intent.json before allowing UI implementation work.');
|
|
412
|
-
console.log('Upgrade synchronizes governance assets, but it does not author project-specific
|
|
368
|
+
console.log('Upgrade synchronizes governance assets and can seed docs/design-intent.json, but it does not author project-specific docs/DESIGN.md automatically.');
|
|
413
369
|
}
|
|
414
370
|
|
|
415
371
|
if (upgradeOptions.dryRun) {
|
|
@@ -434,6 +390,15 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
434
390
|
pruneManagedSurface: upgradeOptions.pruneManagedSurface === true,
|
|
435
391
|
managedSurfacePlan,
|
|
436
392
|
});
|
|
393
|
+
const supplementalCreatedFileNames = [];
|
|
394
|
+
|
|
395
|
+
if (shouldSeedDesignIntentOnApply && designIntentSeedContent) {
|
|
396
|
+
const docsDirectoryPath = path.join(resolvedTargetDirectoryPath, 'docs');
|
|
397
|
+
const designIntentTargetPath = path.join(docsDirectoryPath, 'design-intent.json');
|
|
398
|
+
await ensureDirectory(docsDirectoryPath);
|
|
399
|
+
await fs.writeFile(designIntentTargetPath, designIntentSeedContent, 'utf8');
|
|
400
|
+
supplementalCreatedFileNames.push('docs/design-intent.json');
|
|
401
|
+
}
|
|
437
402
|
|
|
438
403
|
await fs.writeFile(currentRulesPath, plannedRulesContent, 'utf8');
|
|
439
404
|
await fs.writeFile(path.join(resolvedTargetDirectoryPath, '.windsurfrules'), plannedRulesContent, 'utf8');
|
|
@@ -471,6 +436,12 @@ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions
|
|
|
471
436
|
governanceSyncResult.updatedFiles.forEach((fileName) => console.log(` [UPDATED] ${fileName}`));
|
|
472
437
|
governanceSyncResult.deletedManagedFiles.forEach((fileName) => console.log(` [DELETED] ${fileName}`));
|
|
473
438
|
}
|
|
439
|
+
if (supplementalCreatedFileNames.length > 0) {
|
|
440
|
+
if (!(governanceSyncResult.updatedFiles.length > 0 || governanceSyncResult.createdFiles.length > 0 || governanceSyncResult.deletedManagedFiles.length > 0)) {
|
|
441
|
+
console.log('\nDetailed changes:');
|
|
442
|
+
}
|
|
443
|
+
supplementalCreatedFileNames.forEach((fileName) => console.log(` [NEW] ${fileName} (seed)`));
|
|
444
|
+
}
|
|
474
445
|
|
|
475
446
|
console.log('\nRefreshed files: .cursorrules, .windsurfrules, .agent-context/state/onboarding-report.json');
|
|
476
447
|
} catch (error) {
|
package/lib/cli/detector.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Depends on: constants.mjs, utils.mjs
|
|
4
4
|
*/
|
|
5
5
|
import fs from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
6
7
|
|
|
7
8
|
import { BLUEPRINT_RECOMMENDATIONS } from './constants.mjs';
|
|
8
9
|
import { toTitleCase } from './utils.mjs';
|
|
@@ -22,6 +23,106 @@ export async function collectProjectMarkers(targetDirectoryPath) {
|
|
|
22
23
|
return markerNames;
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
async function readPackageJsonIfExists(targetDirectoryPath) {
|
|
27
|
+
const packageJsonPath = path.join(targetDirectoryPath, 'package.json');
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8');
|
|
31
|
+
return JSON.parse(packageJsonContent);
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function detectUiScopeSignals({
|
|
38
|
+
targetDirectoryPath,
|
|
39
|
+
selectedStackFileName,
|
|
40
|
+
selectedBlueprintFileName,
|
|
41
|
+
packageManifest = null,
|
|
42
|
+
projectScopeKey = null,
|
|
43
|
+
projectScopeSourceLabel = 'project scope',
|
|
44
|
+
}) {
|
|
45
|
+
const signalReasons = [];
|
|
46
|
+
const markerNames = await collectProjectMarkers(targetDirectoryPath);
|
|
47
|
+
const resolvedPackageManifest = packageManifest || await readPackageJsonIfExists(targetDirectoryPath);
|
|
48
|
+
|
|
49
|
+
const normalizedProjectScopeKey = String(projectScopeKey || '').trim().toLowerCase();
|
|
50
|
+
if (normalizedProjectScopeKey === 'frontend-only' || normalizedProjectScopeKey === 'both') {
|
|
51
|
+
signalReasons.push(`${projectScopeSourceLabel}: ${normalizedProjectScopeKey}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const selectedStackKey = String(selectedStackFileName || '').trim().toLowerCase();
|
|
55
|
+
if (selectedStackKey === 'react-native.md' || selectedStackKey === 'flutter.md') {
|
|
56
|
+
signalReasons.push(`selected stack implies UI runtime: ${selectedStackKey}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const selectedBlueprintKey = String(selectedBlueprintFileName || '').trim().toLowerCase();
|
|
60
|
+
if (selectedBlueprintKey.includes('frontend') || selectedBlueprintKey.includes('landing') || selectedBlueprintKey.includes('mobile-app')) {
|
|
61
|
+
signalReasons.push(`selected blueprint implies UI scope: ${selectedBlueprintKey}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const directUiMarkerNames = [
|
|
65
|
+
'next.config.js',
|
|
66
|
+
'next.config.mjs',
|
|
67
|
+
'next.config.ts',
|
|
68
|
+
'tailwind.config.js',
|
|
69
|
+
'tailwind.config.mjs',
|
|
70
|
+
'tailwind.config.ts',
|
|
71
|
+
'vite.config.js',
|
|
72
|
+
'vite.config.mjs',
|
|
73
|
+
'vite.config.ts',
|
|
74
|
+
'react-native.config.js',
|
|
75
|
+
'app',
|
|
76
|
+
'pages',
|
|
77
|
+
'components',
|
|
78
|
+
'public',
|
|
79
|
+
'styles',
|
|
80
|
+
'android',
|
|
81
|
+
'ios',
|
|
82
|
+
'index.html',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const detectedUiMarkers = directUiMarkerNames.filter((markerName) => markerNames.has(markerName));
|
|
86
|
+
if (detectedUiMarkers.length > 0) {
|
|
87
|
+
signalReasons.push(`ui markers: ${detectedUiMarkers.join(', ')}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const dependencySource = {
|
|
91
|
+
...(resolvedPackageManifest?.dependencies || {}),
|
|
92
|
+
...(resolvedPackageManifest?.devDependencies || {}),
|
|
93
|
+
};
|
|
94
|
+
const detectableUiDependencies = [
|
|
95
|
+
'next',
|
|
96
|
+
'react',
|
|
97
|
+
'react-dom',
|
|
98
|
+
'react-native',
|
|
99
|
+
'expo',
|
|
100
|
+
'tailwindcss',
|
|
101
|
+
];
|
|
102
|
+
const detectedUiDependencies = detectableUiDependencies.filter((dependencyName) => dependencySource[dependencyName]);
|
|
103
|
+
if (detectedUiDependencies.length > 0) {
|
|
104
|
+
signalReasons.push(`ui dependencies: ${detectedUiDependencies.join(', ')}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const hasStrongUiMarker = detectedUiMarkers.some((markerName) => (
|
|
108
|
+
markerName.startsWith('next.config')
|
|
109
|
+
|| markerName === 'react-native.config.js'
|
|
110
|
+
|| markerName === 'android'
|
|
111
|
+
|| markerName === 'ios'
|
|
112
|
+
));
|
|
113
|
+
const hasUiDependencies = detectedUiDependencies.length > 0;
|
|
114
|
+
const hasStructuralUiMarkers = detectedUiMarkers.length >= 2;
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
isUiScopeLikely: signalReasons.length > 0
|
|
118
|
+
&& (hasStrongUiMarker || hasUiDependencies || hasStructuralUiMarkers || normalizedProjectScopeKey.length > 0),
|
|
119
|
+
signalReasons,
|
|
120
|
+
detectedUiMarkers,
|
|
121
|
+
detectedUiDependencies,
|
|
122
|
+
packageManifest: resolvedPackageManifest,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
25
126
|
export async function detectProjectContext(targetDirectoryPath) {
|
|
26
127
|
const markerNames = await collectProjectMarkers(targetDirectoryPath);
|
|
27
128
|
const detectionCandidates = [];
|
|
@@ -373,21 +373,30 @@ function inferDesignKeywords(discoveryAnswers) {
|
|
|
373
373
|
};
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
-
function
|
|
377
|
-
|
|
376
|
+
export function buildDesignIntentSeedFromSignals({
|
|
377
|
+
projectName,
|
|
378
|
+
projectDescription,
|
|
379
|
+
primaryDomain,
|
|
380
|
+
features = [],
|
|
378
381
|
initContext,
|
|
379
|
-
architectureRecommendation,
|
|
382
|
+
architectureRecommendation = null,
|
|
383
|
+
status = 'seed-needs-design-synthesis',
|
|
384
|
+
supplementalFields = {},
|
|
380
385
|
}) {
|
|
381
|
-
const inferredKeywords = inferDesignKeywords(
|
|
386
|
+
const inferredKeywords = inferDesignKeywords({
|
|
387
|
+
projectDescription,
|
|
388
|
+
primaryDomain,
|
|
389
|
+
features,
|
|
390
|
+
});
|
|
382
391
|
const designSignals = architectureRecommendation?.designGuidance?.normalizedSignals || null;
|
|
383
392
|
|
|
384
393
|
return `${JSON.stringify({
|
|
385
394
|
mode: 'dynamic',
|
|
386
|
-
status
|
|
395
|
+
status,
|
|
387
396
|
project: {
|
|
388
|
-
name:
|
|
389
|
-
context:
|
|
390
|
-
domain:
|
|
397
|
+
name: projectName,
|
|
398
|
+
context: projectDescription,
|
|
399
|
+
domain: primaryDomain,
|
|
391
400
|
stack: toTitleCase(initContext.stackFileName),
|
|
392
401
|
blueprint: toTitleCase(initContext.blueprintFileName),
|
|
393
402
|
},
|
|
@@ -431,9 +440,26 @@ function buildDesignIntentSeed({
|
|
|
431
440
|
bootstrapPrompt: '.agent-context/prompts/bootstrap-design.md',
|
|
432
441
|
},
|
|
433
442
|
architectSignals: designSignals,
|
|
443
|
+
...supplementalFields,
|
|
434
444
|
}, null, 2)}\n`;
|
|
435
445
|
}
|
|
436
446
|
|
|
447
|
+
function buildDesignIntentSeed({
|
|
448
|
+
discoveryAnswers,
|
|
449
|
+
initContext,
|
|
450
|
+
architectureRecommendation,
|
|
451
|
+
}) {
|
|
452
|
+
return buildDesignIntentSeedFromSignals({
|
|
453
|
+
projectName: discoveryAnswers.projectName,
|
|
454
|
+
projectDescription: discoveryAnswers.projectDescription,
|
|
455
|
+
primaryDomain: discoveryAnswers.primaryDomain,
|
|
456
|
+
features: discoveryAnswers.features,
|
|
457
|
+
initContext,
|
|
458
|
+
architectureRecommendation,
|
|
459
|
+
status: 'seed-needs-design-synthesis',
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
437
463
|
function buildProjectContextBootstrapPrompt({
|
|
438
464
|
discoveryAnswers,
|
|
439
465
|
initContext,
|
package/package.json
CHANGED
package/scripts/validate.mjs
CHANGED
|
@@ -261,8 +261,10 @@ const REQUIRED_UPGRADE_UI_CONTRACT_WARNING_SNIPPETS = [
|
|
|
261
261
|
snippets: [
|
|
262
262
|
'UI/frontend scope was detected, but the dynamic design contract is incomplete:',
|
|
263
263
|
'docs/design-intent.json',
|
|
264
|
-
'
|
|
265
|
-
'
|
|
264
|
+
'Planned seed on apply: docs/design-intent.json',
|
|
265
|
+
'Upgrade synchronizes governance assets and can seed docs/design-intent.json, but it does not author project-specific docs/DESIGN.md automatically.',
|
|
266
|
+
'detectUiScopeSignals',
|
|
267
|
+
'seed-generated-during-upgrade',
|
|
266
268
|
],
|
|
267
269
|
},
|
|
268
270
|
];
|