@ryuenn3123/agentic-senior-core 3.0.7 → 3.0.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.
- package/.agent-context/prompts/bootstrap-design.md +6 -4
- package/.agent-context/prompts/init-project.md +4 -4
- 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
|
@@ -10,10 +10,12 @@ This contract is a structure and reasoning system, not a fixed visual template.
|
|
|
10
10
|
The agent must:
|
|
11
11
|
1. Read [AGENTS.md](../../AGENTS.md) for project context and team roles.
|
|
12
12
|
2. Scan all files in [.agent-context/rules/](../rules/) for UI/UX and accessibility standards.
|
|
13
|
-
3.
|
|
13
|
+
3. Use repository evidence from [.agent-context/state/onboarding-report.json](../state/onboarding-report.json), existing UI code, product copy, route names, component names, and any existing `docs/*` project docs to infer architecture and product background.
|
|
14
14
|
4. If [docs/DESIGN.md](../../docs/DESIGN.md) or `docs/design-intent.json` already exists, check for drift and improve them instead of rewriting blindly.
|
|
15
|
-
5.
|
|
16
|
-
6.
|
|
15
|
+
5. If context is incomplete, write explicit assumptions and reversible design bets instead of defaulting to generic SaaS output.
|
|
16
|
+
6. Explore multiple plausible design directions internally, then commit to one cohesive direction with clear rationale tied to the product context.
|
|
17
|
+
7. Treat any example structure or stylistic inspiration as non-normative. Use it only to judge depth and clarity, never to copy a visual language directly.
|
|
18
|
+
8. All references to docs or rules must be clickable markdown links.
|
|
17
19
|
|
|
18
20
|
Required `docs/DESIGN.md` sections:
|
|
19
21
|
1. Design Intent and Product Personality
|
|
@@ -44,5 +46,5 @@ Required `docs/design-intent.json` fields:
|
|
|
44
46
|
Output:
|
|
45
47
|
- Create or update both `docs/DESIGN.md` and `docs/design-intent.json`.
|
|
46
48
|
- Keep both files synchronized: the markdown explains the why, the JSON captures the contract in machine-readable form.
|
|
47
|
-
- Use practical, modern, accessible language grounded in the project, not generic SaaS defaults.
|
|
49
|
+
- Use practical, modern, accessible language grounded in the project, not generic SaaS defaults or copycat brand systems.
|
|
48
50
|
- Wait for user approval before generating Figma or code assets.
|
|
@@ -6,7 +6,7 @@ This prompt boots a repository with strict rules operations context (Federated G
|
|
|
6
6
|
When a new project is created or initialized, the agent should automatically:
|
|
7
7
|
1. Read [AGENTS.md](../../AGENTS.md) to understand available roles and knowledge base.
|
|
8
8
|
2. Scan all files in [.agent-context/rules/](../rules/) for mandatory engineering standards.
|
|
9
|
-
3. Review dynamic stack and architecture signals from [
|
|
9
|
+
3. Review dynamic stack and architecture signals from [.agent-context/state/onboarding-report.json](../state/onboarding-report.json), [.agent-context/state/stack-research-snapshot.json](../state/stack-research-snapshot.json), available stack and blueprint sources, and task constraints.
|
|
10
10
|
|
|
11
11
|
## Architect Mode (Recommended)
|
|
12
12
|
If the user describes a project or feature, the agent should:
|
|
@@ -19,7 +19,7 @@ If the user describes a project or feature, the agent should:
|
|
|
19
19
|
If the user specifies a framework/blueprint, the agent should:
|
|
20
20
|
1. Read [AGENTS.md](../../AGENTS.md) for role context.
|
|
21
21
|
2. Scan all files in [.agent-context/rules/](../rules/) for engineering standards.
|
|
22
|
-
3. Reference [
|
|
22
|
+
3. Reference [.agent-context/state/onboarding-report.json](../state/onboarding-report.json), [.cursorrules](../../.cursorrules), and [.windsurfrules](../../.windsurfrules) for the active stack and blueprint guidance already applied to this project.
|
|
23
23
|
4. Scaffold the initial project structure following the blueprint exactly:
|
|
24
24
|
- Create all directories and files from the blueprint
|
|
25
25
|
- Set up environment config and validation (e.g., Zod, Pydantic, FluentValidation)
|
|
@@ -32,7 +32,7 @@ If the user specifies a framework/blueprint, the agent should:
|
|
|
32
32
|
- Every dependency must be justified per [efficiency-vs-hype.md](../rules/efficiency-vs-hype.md)
|
|
33
33
|
|
|
34
34
|
## Stacks & Blueprints Reference
|
|
35
|
-
See [
|
|
35
|
+
See [.agent-context/state/onboarding-report.json](../state/onboarding-report.json), [.cursorrules](../../.cursorrules), and [.windsurfrules](../../.windsurfrules) for the latest shipped stack and blueprint context.
|
|
36
36
|
|
|
37
37
|
## UI/UX Bootstrap
|
|
38
38
|
When a user requests frontend or UI/UX design, the agent should automatically execute the [bootstrap-design.md](./bootstrap-design.md) prompt to synthesize a dynamic design contract (`docs/DESIGN.md` + `docs/design-intent.json`).
|
|
@@ -93,7 +93,7 @@ Every dependency must be justified per [efficiency-vs-hype.md](../rules/efficien
|
|
|
93
93
|
|
|
94
94
|
## Stacks & Blueprints Reference
|
|
95
95
|
|
|
96
|
-
See [
|
|
96
|
+
See [.agent-context/state/onboarding-report.json](../state/onboarding-report.json), [.cursorrules](../../.cursorrules), and [.windsurfrules](../../.windsurfrules) for the latest shipped stack and blueprint context.
|
|
97
97
|
|
|
98
98
|
---
|
|
99
99
|
|
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.9
|
|
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.9
|
|
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.9 (2026-04-20).
|
|
14
14
|
|
|
15
|
-
Highlights in 3.0.
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- Init
|
|
15
|
+
Highlights in 3.0.9:
|
|
16
|
+
- Design bootstrap prompts now rely on shipped project evidence instead of assuming extra docs exist in every initialized repository.
|
|
17
|
+
- Dynamic UI design guidance now emphasizes structure, rationale, and anti-generic constraints without anchoring to example brand systems.
|
|
18
|
+
- Init prompts now point agents at the active onboarding and compiled-rule context that actually exists in target projects.
|
|
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
|
];
|