@fiodos/cli 0.1.29 → 0.1.30
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/package.json +1 -1
- package/src/collect.js +75 -8
- package/src/wireHandlers.js +7 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fiodos/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"description": "Fiodos CLI — analyzes your app's source code and generates the in-app voice-agent manifest, then wires the orb. Powers `npx @fiodos/cli analyze`.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
package/src/collect.js
CHANGED
|
@@ -80,14 +80,27 @@ function tierFor(rel, platform) {
|
|
|
80
80
|
if (/^sections\//.test(p)) return 2;
|
|
81
81
|
return 3; // snippets/, config/ (settings_data.json can be huge), rest
|
|
82
82
|
}
|
|
83
|
+
return tierForApp(p);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function tierForApp(p) {
|
|
83
87
|
if (/^(src\/)?app\//.test(p)) return 0; // expo-router / Next screens
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
// `sections/` and `panels/` are SCREEN BODIES in shell-based apps (a Shell
|
|
89
|
+
// page switches between section components): they carry the visible controls
|
|
90
|
+
// (color pickers, toggles) the analyzer must read to emit their actions.
|
|
91
|
+
// Ranking them as generic components (tier 3) silently dropped them under
|
|
92
|
+
// budget pressure and whole screens became invisible to the AI.
|
|
93
|
+
if (/(^|\/)(screens?|pages?|views?|routes?|sections?|panels?|activities|fragments)\//i.test(p)) return 0;
|
|
94
|
+
if (/(screen|page|view|activity|fragment|section)\.(dart|kt|swift)$/i.test(p)) return 0;
|
|
95
|
+
// Central state gets its OWN tier right after screens: the providers/stores
|
|
96
|
+
// define the setters that make screen controls wire-able actions. Sharing a
|
|
97
|
+
// tier with the (numerous) service/lib files let alphabetical fill starve
|
|
98
|
+
// out AppProvider-style files, and the AI had to guess every handler name.
|
|
86
99
|
if (/(^|\/)(state|store|stores|context|contexts|redux|providers?|blocs?|cubits?|viewmodels?)\//i.test(p)) return 1;
|
|
87
100
|
if (/(viewmodel|notifier|bloc|cubit|store|controller)\.(dart|kt|swift)$/i.test(p)) return 1;
|
|
88
|
-
if (/(^|\/)(services?|api|db|data|domain|agent|handlers?|actions?|lib|utils|repositories|repository|usecases?)\//i.test(p)) return
|
|
89
|
-
if (/(^|\/)(hooks)\//.test(p)) return
|
|
90
|
-
return
|
|
101
|
+
if (/(^|\/)(services?|api|db|data|domain|agent|handlers?|actions?|lib|utils|repositories|repository|usecases?)\//i.test(p)) return 2;
|
|
102
|
+
if (/(^|\/)(hooks)\//.test(p)) return 3;
|
|
103
|
+
return 4; // generic components / ui
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
function collectFiles(appRoot, { maxInputTokens = 150_000, platform = 'mobile' } = {}) {
|
|
@@ -108,7 +121,21 @@ function collectFiles(appRoot, { maxInputTokens = 150_000, platform = 'mobile' }
|
|
|
108
121
|
if (!codeExt.has(path.extname(entry.name))) continue;
|
|
109
122
|
if (SKIP_FILES.test(rel)) continue;
|
|
110
123
|
if (isNative && SKIP_NATIVE_FILES.test(rel)) continue;
|
|
111
|
-
|
|
124
|
+
let content = fs.readFileSync(full, 'utf8');
|
|
125
|
+
if (platform === 'shopify' && /^locales\//.test(rel.replace(/\\/g, '/'))) {
|
|
126
|
+
// Theme-editor translations (merchant-facing settings strings), not
|
|
127
|
+
// storefront language evidence — pure noise for the analysis.
|
|
128
|
+
if (/\.schema\.json$/.test(rel)) continue;
|
|
129
|
+
// Non-default storefront locales: what matters is THAT the language is
|
|
130
|
+
// published (the filename carries the code) — that is the evidence the
|
|
131
|
+
// MULTILINGUAL rule needs to emit appLanguages/bubblePrompts. Sending
|
|
132
|
+
// each file whole (tens of KB × dozens of locales) blew the budget and
|
|
133
|
+
// the alphabetical cut dropped exactly the important ones (en/es
|
|
134
|
+
// arrived after bg/cs/da…), so the AI saw a "monolingual" store.
|
|
135
|
+
if (!/\.default\.json$/.test(rel)) {
|
|
136
|
+
content = '{"__fyodos_note": "published storefront locale (content elided; this language IS offered to shoppers — count it in appLanguages/bubblePrompts)"}';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
112
139
|
files.push({ rel, content, tier: tierFor(rel, platform), chars: content.length });
|
|
113
140
|
}
|
|
114
141
|
};
|
|
@@ -118,10 +145,48 @@ function collectFiles(appRoot, { maxInputTokens = 150_000, platform = 'mobile' }
|
|
|
118
145
|
files.sort((a, b) => a.tier - b.tier || a.rel.localeCompare(b.rel));
|
|
119
146
|
|
|
120
147
|
const budgetChars = maxInputTokens * CHARS_PER_TOKEN;
|
|
148
|
+
|
|
149
|
+
// Floor-reserved fill. A pure greedy fill let a fat tier 0 (many large
|
|
150
|
+
// screen files) consume the WHOLE budget and push out tier 1 entirely —
|
|
151
|
+
// i.e. the app's central state/providers (AppProvider, stores) vanished
|
|
152
|
+
// from the analysis, so the AI could not see the setters that make
|
|
153
|
+
// configuration controls real actions and had to guess every handler name.
|
|
154
|
+
// Each lower tier gets a guaranteed FLOOR (share of the total budget,
|
|
155
|
+
// capped at its real demand); screens still get everything left over, and a
|
|
156
|
+
// final backfill hands unused room to deferred files in tier order.
|
|
157
|
+
const FLOOR_SHARE = { 1: 0.2, 2: 0.05, 3: 0.02, 4: 0.02 };
|
|
158
|
+
const demand = new Map();
|
|
159
|
+
for (const f of files) demand.set(f.tier, (demand.get(f.tier) ?? 0) + f.chars);
|
|
160
|
+
|
|
121
161
|
const included = [];
|
|
122
|
-
const
|
|
162
|
+
const deferred = [];
|
|
123
163
|
let used = 0;
|
|
124
|
-
|
|
164
|
+
const tiers = [...demand.keys()].sort((a, b) => a - b);
|
|
165
|
+
for (const tier of tiers) {
|
|
166
|
+
// Reserve, for every LOWER-priority tier that still has files, the smaller
|
|
167
|
+
// of its floor and what it actually needs.
|
|
168
|
+
let reserved = 0;
|
|
169
|
+
for (const t of tiers) {
|
|
170
|
+
if (t <= tier) continue;
|
|
171
|
+
reserved += Math.min((FLOOR_SHARE[t] ?? 0) * budgetChars, demand.get(t));
|
|
172
|
+
}
|
|
173
|
+
const available = budgetChars - used - reserved;
|
|
174
|
+
let tierUsed = 0;
|
|
175
|
+
for (const f of files) {
|
|
176
|
+
if (f.tier !== tier) continue;
|
|
177
|
+
if (tierUsed + f.chars <= available) {
|
|
178
|
+
included.push(f);
|
|
179
|
+
tierUsed += f.chars;
|
|
180
|
+
} else {
|
|
181
|
+
deferred.push(f);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
used += tierUsed;
|
|
185
|
+
}
|
|
186
|
+
// Backfill: any room the floors did not consume goes back to the deferred
|
|
187
|
+
// files, highest tier priority first.
|
|
188
|
+
const omitted = [];
|
|
189
|
+
for (const f of deferred) {
|
|
125
190
|
if (used + f.chars <= budgetChars) {
|
|
126
191
|
included.push(f);
|
|
127
192
|
used += f.chars;
|
|
@@ -129,6 +194,8 @@ function collectFiles(appRoot, { maxInputTokens = 150_000, platform = 'mobile' }
|
|
|
129
194
|
omitted.push({ rel: f.rel, chars: f.chars, tier: f.tier });
|
|
130
195
|
}
|
|
131
196
|
}
|
|
197
|
+
// Restore deterministic tier/path order after the backfill.
|
|
198
|
+
included.sort((a, b) => a.tier - b.tier || a.rel.localeCompare(b.rel));
|
|
132
199
|
|
|
133
200
|
return {
|
|
134
201
|
included,
|
package/src/wireHandlers.js
CHANGED
|
@@ -1334,9 +1334,14 @@ function stripFiodosBlocks(content) {
|
|
|
1334
1334
|
);
|
|
1335
1335
|
}
|
|
1336
1336
|
|
|
1337
|
-
/** Insert an import block right after the file's last top-level import
|
|
1337
|
+
/** Insert an import block right after the file's last top-level import statement.
|
|
1338
|
+
* Matches the WHOLE statement through its module string — a per-line regex put
|
|
1339
|
+
* the block INSIDE multi-line imports (`import {\n a,\n} from 'x';`), corrupting
|
|
1340
|
+
* the file (TS1003 "Identifier expected") and failing the wiring of every action
|
|
1341
|
+
* in that file. `[^'"]*` cannot skip a quote, so the match always stops at the
|
|
1342
|
+
* first quoted module specifier, then runs to that line's end. */
|
|
1338
1343
|
function insertImportBlock(content, importBlock) {
|
|
1339
|
-
const importLineRe = /^[ \t]*import\b[^\n]*\n/gm;
|
|
1344
|
+
const importLineRe = /^[ \t]*import\b[^'"]*['"][^'"]+['"][^\n]*\n/gm;
|
|
1340
1345
|
let lastEnd = -1;
|
|
1341
1346
|
let m;
|
|
1342
1347
|
while ((m = importLineRe.exec(content))) lastEnd = m.index + m[0].length;
|