@fiodos/cli 0.1.0 → 0.1.3
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/README.md +17 -0
- package/package.json +1 -1
- package/src/index.js +82 -4
- package/src/postWireTest.js +3 -3
- package/src/renderProbe.js +68 -22
- package/src/renderProbeVue.js +12 -8
- package/src/verifyWire.js +7 -7
- package/src/wireHandlers.js +179 -180
- package/src/wireWeb.js +43 -8
- package/src/writeEnv.js +230 -0
package/src/wireHandlers.js
CHANGED
|
@@ -315,7 +315,7 @@ function mapParams(targetParams, manifestParams) {
|
|
|
315
315
|
if (mp.required && !targetNames.has(mp.name)) {
|
|
316
316
|
return {
|
|
317
317
|
ok: false,
|
|
318
|
-
reason: `
|
|
318
|
+
reason: `required parameter '${mp.name}' from the manifest does not exist in the function signature`,
|
|
319
319
|
};
|
|
320
320
|
}
|
|
321
321
|
}
|
|
@@ -329,7 +329,7 @@ function mapParams(targetParams, manifestParams) {
|
|
|
329
329
|
}
|
|
330
330
|
return {
|
|
331
331
|
ok: false,
|
|
332
|
-
reason: '
|
|
332
|
+
reason: 'the function uses destructuring or a rest parameter that cannot be mapped safely',
|
|
333
333
|
};
|
|
334
334
|
}
|
|
335
335
|
if (manifestNames.has(tp.name)) {
|
|
@@ -339,7 +339,7 @@ function mapParams(targetParams, manifestParams) {
|
|
|
339
339
|
} else {
|
|
340
340
|
return {
|
|
341
341
|
ok: false,
|
|
342
|
-
reason: `
|
|
342
|
+
reason: `function parameter '${tp.name}' has no equivalent in the manifest`,
|
|
343
343
|
};
|
|
344
344
|
}
|
|
345
345
|
}
|
|
@@ -364,16 +364,16 @@ function relImport(fromDirRel, toFileRel) {
|
|
|
364
364
|
* and every identifier the call reads is either imported or a safe global.
|
|
365
365
|
*/
|
|
366
366
|
function buildAiEntry({ appRoot, fyodosDirRel, base, aiW, manifestParams }) {
|
|
367
|
-
if (!aiW || typeof aiW !== 'object') return { reason: '
|
|
367
|
+
if (!aiW || typeof aiW !== 'object') return { reason: 'the AI proposed no wiring for this action' };
|
|
368
368
|
if (aiW.strategy === 'bridge') {
|
|
369
369
|
return buildAiBridgeEntry({ appRoot, fyodosDirRel, base, aiW });
|
|
370
370
|
}
|
|
371
371
|
if (aiW.strategy && aiW.strategy !== 'module') {
|
|
372
|
-
return { reason: `
|
|
372
|
+
return { reason: `unknown wiring strategy: '${aiW.strategy}'` };
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
const call = String(aiW.call || '').trim();
|
|
376
|
-
if (!call) return { reason: '
|
|
376
|
+
if (!call) return { reason: 'the AI did not provide a verifiable call expression' };
|
|
377
377
|
|
|
378
378
|
const rawImports = Array.isArray(aiW.imports) ? aiW.imports : [];
|
|
379
379
|
// 1) Every required symbol must really exist where the AI says it does.
|
|
@@ -381,7 +381,7 @@ function buildAiEntry({ appRoot, fyodosDirRel, base, aiW, manifestParams }) {
|
|
|
381
381
|
for (const r of required) {
|
|
382
382
|
if (!fileHasSymbol(appRoot, r && r.file, r && r.name)) {
|
|
383
383
|
return {
|
|
384
|
-
reason: `
|
|
384
|
+
reason: `the verifier did not find symbol '${r && r.name}' in '${normRel(r && r.file)}' (the AI proposed it but it does not exist in the code)`,
|
|
385
385
|
};
|
|
386
386
|
}
|
|
387
387
|
}
|
|
@@ -393,14 +393,14 @@ function buildAiEntry({ appRoot, fyodosDirRel, base, aiW, manifestParams }) {
|
|
|
393
393
|
const name = String((imp && imp.name) || '').trim();
|
|
394
394
|
const from = normRel(imp && imp.from);
|
|
395
395
|
const kind = (imp && imp.kind) || 'named';
|
|
396
|
-
if (!name || !from) return { reason: '
|
|
396
|
+
if (!name || !from) return { reason: 'an import proposed by the AI is incomplete (missing name/from)' };
|
|
397
397
|
if (!fs.existsSync(path.join(appRoot, from))) {
|
|
398
|
-
return { reason: `
|
|
398
|
+
return { reason: `the proposed import points to a non-existent file: '${from}'` };
|
|
399
399
|
}
|
|
400
400
|
// For named imports the symbol must be present in that file; default/namespace
|
|
401
401
|
// bind the module's default/whole export so only file existence is checked.
|
|
402
402
|
if (kind === 'named' && !fileHasSymbol(appRoot, from, name)) {
|
|
403
|
-
return { reason: `
|
|
403
|
+
return { reason: `named import '${name}' does not exist in '${from}'` };
|
|
404
404
|
}
|
|
405
405
|
imports.push({ kind, name, importPath: relImport(fyodosDirRel, from) });
|
|
406
406
|
importedNames.add(name);
|
|
@@ -411,10 +411,10 @@ function buildAiEntry({ appRoot, fyodosDirRel, base, aiW, manifestParams }) {
|
|
|
411
411
|
if (detached) {
|
|
412
412
|
return {
|
|
413
413
|
reason:
|
|
414
|
-
`
|
|
415
|
-
'
|
|
416
|
-
'
|
|
417
|
-
'
|
|
414
|
+
`the expression instantiates '${detached}' with 'new' and uses it as the receiver; ` +
|
|
415
|
+
'if it is an injected service/state (DI) or a singleton, that new instance ' +
|
|
416
|
+
'is disconnected from the one the UI sees and the action would have no observable effect. ' +
|
|
417
|
+
'It needs a bridge to the real singleton instead of instantiating it.',
|
|
418
418
|
};
|
|
419
419
|
}
|
|
420
420
|
|
|
@@ -424,7 +424,7 @@ function buildAiEntry({ appRoot, fyodosDirRel, base, aiW, manifestParams }) {
|
|
|
424
424
|
for (const id of freeIdentifiers(call)) {
|
|
425
425
|
if (JS_KEYWORDS.has(id) || importedNames.has(id) || SAFE_GLOBALS.has(id) || locals.has(id)) continue;
|
|
426
426
|
return {
|
|
427
|
-
reason: `
|
|
427
|
+
reason: `the AI's expression references '${id}', which is neither imported nor a known global/local variable; not wired to avoid calling something that doesn't exist`,
|
|
428
428
|
};
|
|
429
429
|
}
|
|
430
430
|
|
|
@@ -459,21 +459,21 @@ function buildAiBridgeEntry({ appRoot, fyodosDirRel, base, aiW }) {
|
|
|
459
459
|
const invoke = String(b.invoke || '').trim();
|
|
460
460
|
const why = aiW.bridgeReason ? ` (${aiW.bridgeReason})` : '';
|
|
461
461
|
|
|
462
|
-
if (!file) return { reason: `
|
|
462
|
+
if (!file) return { reason: `the AI marked a bridge but did not indicate the component file${why}` };
|
|
463
463
|
const content = readFileSafe(appRoot, file);
|
|
464
|
-
if (content == null) return { reason: `
|
|
465
|
-
if (!invoke) return { reason: `
|
|
466
|
-
if (!anchor) return { reason: `
|
|
464
|
+
if (content == null) return { reason: `could not read the component '${file}' to apply the bridge` };
|
|
465
|
+
if (!invoke) return { reason: `the AI did not provide the bridge 'invoke' expression for '${file}'` };
|
|
466
|
+
if (!anchor) return { reason: `the AI did not provide an insertion anchor in '${file}'` };
|
|
467
467
|
|
|
468
468
|
const occ = countOccurrences(content, anchor);
|
|
469
|
-
if (occ === 0) return { reason: `
|
|
470
|
-
if (occ > 1) return { reason: `
|
|
469
|
+
if (occ === 0) return { reason: `the insertion anchor does not appear in '${file}'; cannot place the bridge safely` };
|
|
470
|
+
if (occ > 1) return { reason: `the insertion anchor appears ${occ} times in '${file}'; it is ambiguous, not inserted blindly` };
|
|
471
471
|
|
|
472
472
|
// requiredSymbols must really exist.
|
|
473
473
|
const required = Array.isArray(aiW.requiredSymbols) ? aiW.requiredSymbols : [];
|
|
474
474
|
for (const r of required) {
|
|
475
475
|
if (!fileHasSymbol(appRoot, r && r.file, r && r.name)) {
|
|
476
|
-
return { reason: `
|
|
476
|
+
return { reason: `the verifier did not find symbol '${r && r.name}' in '${normRel(r && r.file)}' (bridge not verifiable)` };
|
|
477
477
|
}
|
|
478
478
|
}
|
|
479
479
|
|
|
@@ -482,8 +482,8 @@ function buildAiBridgeEntry({ appRoot, fyodosDirRel, base, aiW }) {
|
|
|
482
482
|
if (detached) {
|
|
483
483
|
return {
|
|
484
484
|
reason:
|
|
485
|
-
`
|
|
486
|
-
'
|
|
485
|
+
`the bridge instantiates '${detached}' with 'new' and uses it as the receiver; that creates an ` +
|
|
486
|
+
'instance disconnected from the one the UI sees. It must use the real instance (e.g. this.<service>), not instantiate it.',
|
|
487
487
|
};
|
|
488
488
|
}
|
|
489
489
|
|
|
@@ -494,9 +494,9 @@ function buildAiBridgeEntry({ appRoot, fyodosDirRel, base, aiW }) {
|
|
|
494
494
|
const name = String((imp && imp.name) || '').trim();
|
|
495
495
|
const from = normRel(imp && imp.from);
|
|
496
496
|
const kind = (imp && imp.kind) || 'named';
|
|
497
|
-
if (!name || !from) return { reason: '
|
|
498
|
-
if (!fs.existsSync(path.join(appRoot, from))) return { reason: `
|
|
499
|
-
if (kind === 'named' && !fileHasSymbol(appRoot, from, name)) return { reason: `
|
|
497
|
+
if (!name || !from) return { reason: 'a bridge import is incomplete (missing name/from)' };
|
|
498
|
+
if (!fs.existsSync(path.join(appRoot, from))) return { reason: `the bridge import points to a non-existent file: '${from}'` };
|
|
499
|
+
if (kind === 'named' && !fileHasSymbol(appRoot, from, name)) return { reason: `named import '${name}' does not exist in '${from}'` };
|
|
500
500
|
imports.push({ kind, name, importPath: relImport(path.dirname(file), from) });
|
|
501
501
|
importedNames.add(name);
|
|
502
502
|
}
|
|
@@ -509,7 +509,7 @@ function buildAiBridgeEntry({ appRoot, fyodosDirRel, base, aiW }) {
|
|
|
509
509
|
if (id === 'args' || JS_KEYWORDS.has(id) || SAFE_GLOBALS.has(id) || locals.has(id) || importedNames.has(id)) continue;
|
|
510
510
|
if (fileHasSymbol(appRoot, file, id)) continue;
|
|
511
511
|
return {
|
|
512
|
-
reason: `
|
|
512
|
+
reason: `the bridge references '${id}', which is not in the component '${file}' scope and is not imported; not wired to avoid breaking the code`,
|
|
513
513
|
};
|
|
514
514
|
}
|
|
515
515
|
|
|
@@ -541,7 +541,7 @@ function buildAiBridgeEntry({ appRoot, fyodosDirRel, base, aiW }) {
|
|
|
541
541
|
*/
|
|
542
542
|
function buildMechanicalEntry({ appRoot, fyodosDirRel, base, manifestParams }) {
|
|
543
543
|
const { file: evFile, handler } = base;
|
|
544
|
-
if (!evFile) return { reason: '
|
|
544
|
+
if (!evFile) return { reason: 'the analysis did not provide an evidence file for this action' };
|
|
545
545
|
const absFile = path.join(appRoot, evFile);
|
|
546
546
|
let content = null;
|
|
547
547
|
if (fs.existsSync(absFile)) {
|
|
@@ -551,21 +551,21 @@ function buildMechanicalEntry({ appRoot, fyodosDirRel, base, manifestParams }) {
|
|
|
551
551
|
content = null;
|
|
552
552
|
}
|
|
553
553
|
}
|
|
554
|
-
if (!content) return { reason: `
|
|
554
|
+
if (!content) return { reason: `could not read the evidence file '${evFile}'` };
|
|
555
555
|
|
|
556
556
|
const exported = findExportedFunction(content, handler);
|
|
557
557
|
const store = exported ? null : findStoreMethod(content, handler);
|
|
558
558
|
if (!exported && !store) {
|
|
559
559
|
return {
|
|
560
560
|
reason:
|
|
561
|
-
`
|
|
562
|
-
'
|
|
561
|
+
`'${handler}' was not found as an exported function or a store method in '${evFile}'. ` +
|
|
562
|
+
'It may live in a component, a class or a hook/context. Not wired blindly.',
|
|
563
563
|
};
|
|
564
564
|
}
|
|
565
565
|
|
|
566
566
|
const targetParams = (exported || store).params;
|
|
567
567
|
const mapped = mapParams(targetParams, manifestParams);
|
|
568
|
-
if (!mapped.ok) return { reason: `
|
|
568
|
+
if (!mapped.ok) return { reason: `signature not safely mappable: ${mapped.reason}` };
|
|
569
569
|
|
|
570
570
|
const importPath = relImport(fyodosDirRel, evFile);
|
|
571
571
|
if (exported) {
|
|
@@ -724,7 +724,7 @@ function buildRegistryModule(plan, opts = {}) {
|
|
|
724
724
|
const takesParams = e.usesParams != null ? e.usesParams : /\bparams\b/.test(e.callExpr);
|
|
725
725
|
const arg = takesParams ? `params${paramsType}` : `_params${paramsType}`;
|
|
726
726
|
const confNote = e.requireConfirmation
|
|
727
|
-
? '\n //
|
|
727
|
+
? '\n // Confirmation action: the Fiodos engine already asked for confirmation BEFORE calling here.'
|
|
728
728
|
: '';
|
|
729
729
|
// `: unknown` lets the truthiness test below be legal even when the wired
|
|
730
730
|
// call returns void (e.g. db.delete(...)) — otherwise TS errors on `result &&`.
|
|
@@ -751,13 +751,12 @@ function buildRegistryModule(plan, opts = {}) {
|
|
|
751
751
|
const header =
|
|
752
752
|
`/**\n` +
|
|
753
753
|
` * ${GENERATED_MARKER}.\n` +
|
|
754
|
-
` *
|
|
755
|
-
` *
|
|
754
|
+
` * Do not edit by hand: re-run the Fiodos installer to regenerate it.\n` +
|
|
755
|
+
` * Read ${DOC_BASENAME} (same folder) to see what it wires and why.\n` +
|
|
756
756
|
` *\n` +
|
|
757
|
-
` *
|
|
758
|
-
` *
|
|
759
|
-
` *
|
|
760
|
-
` * cualquier handler de aquí.\n` +
|
|
757
|
+
` * SECURITY: this file only CALLS your functions. It never decides whether a\n` +
|
|
758
|
+
` * sensitive action runs — the Fiodos engine applies the manifest confirmations\n` +
|
|
759
|
+
` * (requireConfirmation / voiceConfirmation) BEFORE invoking any handler here.\n` +
|
|
761
760
|
` */\n`;
|
|
762
761
|
|
|
763
762
|
const decl = esm
|
|
@@ -773,8 +772,8 @@ function buildRegistryModule(plan, opts = {}) {
|
|
|
773
772
|
`${handlerText}\n` +
|
|
774
773
|
(manualNotes.length ? `${manualNotes.join('\n')}\n` : '') +
|
|
775
774
|
` },\n` +
|
|
776
|
-
` //
|
|
777
|
-
` //
|
|
775
|
+
` // Idempotency checkers read your app state; implement them by hand if\n` +
|
|
776
|
+
` // an action declares idempotencyCheck (see ${DOC_BASENAME}).\n` +
|
|
778
777
|
` idempotencyCheckers: {},\n` +
|
|
779
778
|
`};\n` +
|
|
780
779
|
(esm ? '' : `\nmodule.exports = { ${REGISTRY_EXPORT} };\n`);
|
|
@@ -794,9 +793,9 @@ function buildBridgeModule(opts = {}) {
|
|
|
794
793
|
const header =
|
|
795
794
|
`/**\n` +
|
|
796
795
|
` * ${GENERATED_MARKER} (bridge holder).\n` +
|
|
797
|
-
` *
|
|
798
|
-
` * handlers
|
|
799
|
-
` *
|
|
796
|
+
` * Do not edit by hand. Your component registers its real functions here and the\n` +
|
|
797
|
+
` * generated handlers invoke them. This is what connects the agent to the state\n` +
|
|
798
|
+
` * living inside your components, without creating disconnected instances.\n` +
|
|
800
799
|
` */\n`;
|
|
801
800
|
if (ts) {
|
|
802
801
|
return (
|
|
@@ -916,130 +915,130 @@ function planComponentEdits(appRoot, plan, opts = {}) {
|
|
|
916
915
|
// ── Document codegen ───────────────────────────────────────────────────────────
|
|
917
916
|
|
|
918
917
|
function confLabel(e) {
|
|
919
|
-
if (!e.requireConfirmation) return 'no
|
|
920
|
-
const mode = e.voiceConfirmation ? ` (
|
|
921
|
-
return `
|
|
918
|
+
if (!e.requireConfirmation) return 'no confirmation required';
|
|
919
|
+
const mode = e.voiceConfirmation ? ` (voice: ${e.voiceConfirmation})` : '';
|
|
920
|
+
return `requires confirmation${mode}`;
|
|
922
921
|
}
|
|
923
922
|
|
|
924
923
|
function buildHandlerDoc(plan, ctx = {}) {
|
|
925
924
|
const {
|
|
926
|
-
appName = '
|
|
925
|
+
appName = 'your app', framework = 'web', registryRel = '', mountSnippet = '',
|
|
927
926
|
bridgeRel = '', edits = [], verification = null,
|
|
928
927
|
} = ctx;
|
|
929
928
|
const L = [];
|
|
930
|
-
L.push('#
|
|
929
|
+
L.push('# Fiodos action wiring (handler wiring)');
|
|
931
930
|
L.push('');
|
|
932
931
|
L.push(
|
|
933
|
-
'
|
|
934
|
-
'
|
|
935
|
-
'**
|
|
932
|
+
'This document describes **exactly** what Fiodos will do to connect the ' +
|
|
933
|
+
'actions detected in your app with the real functions that run them. ' +
|
|
934
|
+
'**Nothing is applied until you accept in the terminal.**',
|
|
936
935
|
);
|
|
937
936
|
L.push('');
|
|
938
937
|
const review = plan.review || plan.manual || [];
|
|
939
938
|
const aiCount = plan.auto.filter((e) => e.source === 'ai').length;
|
|
940
939
|
L.push(`- App: **${appName}**`);
|
|
941
|
-
L.push(`-
|
|
942
|
-
L.push(`-
|
|
943
|
-
`(${aiCount}
|
|
944
|
-
L.push(`-
|
|
940
|
+
L.push(`- Detected framework: **${framework}**`);
|
|
941
|
+
L.push(`- Actions that will be wired automatically: **${plan.auto.length}** ` +
|
|
942
|
+
`(${aiCount} wired by the AI, ${plan.auto.length - aiCount} by the mechanical fallback detector)`);
|
|
943
|
+
L.push(`- Actions that need review: **${review.length}**`);
|
|
945
944
|
L.push('');
|
|
946
|
-
L.push('##
|
|
945
|
+
L.push('## How is this wiring generated?');
|
|
947
946
|
L.push('');
|
|
948
947
|
L.push(
|
|
949
|
-
'
|
|
950
|
-
'
|
|
951
|
-
'
|
|
952
|
-
'
|
|
953
|
-
'
|
|
948
|
+
'The **same AI** that analyzed your code to detect the actions also proposed ' +
|
|
949
|
+
'how to run them: which real function/module to call and how to map the parameters. ' +
|
|
950
|
+
'Then a **mechanical verifier** checks that every referenced symbol ' +
|
|
951
|
+
'really exists in your code before writing anything. If the AI proposes something ' +
|
|
952
|
+
'the verifier cannot find, that action is left **for review**, never wired blindly.',
|
|
954
953
|
);
|
|
955
954
|
L.push('');
|
|
956
955
|
if (Array.isArray(verification) && verification.length) {
|
|
957
|
-
L.push('##
|
|
956
|
+
L.push('## Automatic verification (real effect + retries)');
|
|
958
957
|
L.push('');
|
|
959
958
|
L.push(
|
|
960
|
-
'
|
|
961
|
-
'
|
|
962
|
-
'
|
|
963
|
-
'
|
|
964
|
-
'
|
|
965
|
-
'
|
|
966
|
-
'
|
|
967
|
-
'
|
|
968
|
-
'
|
|
959
|
+
'After your "yes", the install **does not trust** that the wiring compiles: for each ' +
|
|
960
|
+
'action it runs a **wire → verify → diagnose → correct** loop with no ' +
|
|
961
|
+
'human intervention. Verification checks (1) that the project **still ' +
|
|
962
|
+
'builds** with that action wired and, when it is safe to simulate, (2) that ' +
|
|
963
|
+
'invoking the handler **changes the real state** (the task is added, etc.). If an ' +
|
|
964
|
+
'action fails, the AI receives the **concrete error** and retries with corrected ' +
|
|
965
|
+
'wiring (up to several attempts). An action is only marked **ready** when it passes ' +
|
|
966
|
+
'verification; if it is not achieved after the retries, **only that action is ' +
|
|
967
|
+
'reverted** and marked below as "could not be wired" — never left faking it.',
|
|
969
968
|
);
|
|
970
969
|
L.push('');
|
|
971
|
-
L.push('|
|
|
970
|
+
L.push('| Action (intent) | Result | Attempts | Verification | Detail |');
|
|
972
971
|
L.push('| --- | --- | --- | --- | --- |');
|
|
973
972
|
for (const v of verification) {
|
|
974
|
-
const res = v.status === 'ready' ? '✅
|
|
973
|
+
const res = v.status === 'ready' ? '✅ ready' : '❌ could not';
|
|
975
974
|
let lvl = '—';
|
|
976
975
|
if (v.status === 'ready') {
|
|
977
|
-
lvl = v.level === 'effect' ? '
|
|
978
|
-
: v.level === 'unverifiable' ? '⚠️
|
|
979
|
-
: '
|
|
976
|
+
lvl = v.level === 'effect' ? 'real effect (UI)'
|
|
977
|
+
: v.level === 'unverifiable' ? '⚠️ effect NOT verifiable (review by hand)'
|
|
978
|
+
: 'compilation';
|
|
980
979
|
}
|
|
981
980
|
const detail = String(v.detail || v.error || '').replace(/\|/g, '\\|').slice(0, 160);
|
|
982
981
|
L.push(`| \`${v.intent}\` | ${res} | ${v.attempts} | ${lvl} | ${detail} |`);
|
|
983
982
|
}
|
|
984
983
|
L.push('');
|
|
985
|
-
L.push('_"
|
|
986
|
-
'
|
|
987
|
-
'
|
|
988
|
-
'
|
|
989
|
-
'
|
|
990
|
-
'
|
|
991
|
-
'
|
|
992
|
-
'
|
|
993
|
-
'
|
|
984
|
+
L.push('_"real effect (UI)" = the real component was mounted in a test DOM (jsdom) with ' +
|
|
985
|
+
'the framework\'s standard tooling (React: react-dom; Vue: vue + @vue/compiler-sfc), ' +
|
|
986
|
+
'the wired handler was invoked and the **change in the UI/state was observed** (the task appears, ' +
|
|
987
|
+
'the item toggles, etc.). For the data layer (Dexie/store) the effect is observed by running ' +
|
|
988
|
+
'the action against a real instance. "compilation" = it compiles and the real symbol exists and is ' +
|
|
989
|
+
'invoked, but the effect could not be safely simulated here. "⚠️ effect NOT verifiable" = ' +
|
|
990
|
+
'the wiring was applied but the real effect could not be confirmed automatically (component not ' +
|
|
991
|
+
'mountable in isolation, or a sensitive action that is not fired) — **review/test by hand**. ' +
|
|
992
|
+
'Sensitive actions (with confirmation) are **never** fired in the test._');
|
|
994
993
|
L.push('');
|
|
995
994
|
}
|
|
996
995
|
|
|
997
|
-
L.push('##
|
|
996
|
+
L.push('## Why is it needed?');
|
|
998
997
|
L.push('');
|
|
999
998
|
L.push(
|
|
1000
|
-
'
|
|
1001
|
-
'
|
|
1002
|
-
'
|
|
1003
|
-
'
|
|
999
|
+
'The orb is already mounted, but the agent cannot **run** actions until ' +
|
|
1000
|
+
'each manifest action is connected to your app\'s real function. ' +
|
|
1001
|
+
'This wiring is that bridge: it translates each action (e.g. `addToCart`) into a ' +
|
|
1002
|
+
'call to your function (e.g. `useShop.getState().addToCart(...)`).',
|
|
1004
1003
|
);
|
|
1005
1004
|
L.push('');
|
|
1006
1005
|
const bridgeEntries = plan.auto.filter((e) => e.kind === 'bridge');
|
|
1007
|
-
L.push('##
|
|
1006
|
+
L.push('## Which files are created / edited');
|
|
1008
1007
|
L.push('');
|
|
1009
|
-
L.push(`- **
|
|
1008
|
+
L.push(`- **CREATES** \`${registryRel}\` — the generated handler registry.`);
|
|
1010
1009
|
if (bridgeEntries.length && bridgeRel) {
|
|
1011
|
-
L.push(`- **
|
|
1010
|
+
L.push(`- **CREATES** \`${bridgeRel}\` — the "bridge" where your component registers its real functions.`);
|
|
1012
1011
|
}
|
|
1013
1012
|
if (edits.length) {
|
|
1014
|
-
L.push('- **
|
|
1015
|
-
'`FYODOS:BRIDGE`,
|
|
1016
|
-
for (const ed of edits) L.push(` - \`${ed.file}\` —
|
|
1013
|
+
L.push('- **EDITS the following files OF YOURS** (only adds lines marked with ' +
|
|
1014
|
+
'`FYODOS:BRIDGE`, reversible):');
|
|
1015
|
+
for (const ed of edits) L.push(` - \`${ed.file}\` — registers: ${ed.intents.map((i) => `\`${i}\``).join(', ')}`);
|
|
1017
1016
|
} else {
|
|
1018
|
-
L.push('- **No
|
|
1019
|
-
'
|
|
1017
|
+
L.push('- **No existing file of yours is edited** (all actions were ' +
|
|
1018
|
+
'reachable from a module).');
|
|
1020
1019
|
}
|
|
1021
|
-
L.push('-
|
|
1020
|
+
L.push('- To enable it, pass the generated registry to `<FiodosAgent/>` (see "How to enable it").');
|
|
1022
1021
|
L.push('');
|
|
1023
1022
|
|
|
1024
1023
|
if (edits.length) {
|
|
1025
|
-
L.push('##
|
|
1024
|
+
L.push('## EXACT changes inside your components');
|
|
1026
1025
|
L.push('');
|
|
1027
|
-
L.push('
|
|
1028
|
-
'
|
|
1029
|
-
'
|
|
1026
|
+
L.push('These are the lines Fiodos will add to your files (each block sits between ' +
|
|
1027
|
+
'`FYODOS:BRIDGE:START` / `END` markers, so you can remove or revert them without touching ' +
|
|
1028
|
+
'your own code):');
|
|
1030
1029
|
L.push('');
|
|
1031
1030
|
for (const ed of edits) {
|
|
1032
1031
|
L.push(`### \`${ed.file}\``);
|
|
1033
1032
|
L.push('');
|
|
1034
|
-
L.push('
|
|
1033
|
+
L.push('After the last `import` line, this is added:');
|
|
1035
1034
|
L.push('');
|
|
1036
1035
|
L.push('```ts');
|
|
1037
1036
|
L.push(ed.importBlock);
|
|
1038
1037
|
L.push('```');
|
|
1039
1038
|
L.push('');
|
|
1040
1039
|
L.push(ed.file.endsWith('.vue')
|
|
1041
|
-
? '
|
|
1042
|
-
: `
|
|
1040
|
+
? 'Right before `</script>`, this is added:'
|
|
1041
|
+
: `Next to the anchor \`${(ed.anchor || '').trim().slice(0, 80)}\`, this is added:`);
|
|
1043
1042
|
L.push('');
|
|
1044
1043
|
L.push('```ts');
|
|
1045
1044
|
L.push(ed.registerBlock.replace(/^\s+/gm, (s) => s)); // keep as-is
|
|
@@ -1049,9 +1048,9 @@ function buildHandlerDoc(plan, ctx = {}) {
|
|
|
1049
1048
|
}
|
|
1050
1049
|
|
|
1051
1050
|
if (plan.auto.length) {
|
|
1052
|
-
L.push(`##
|
|
1051
|
+
L.push(`## Actions that will be wired automatically (${plan.auto.length})`);
|
|
1053
1052
|
L.push('');
|
|
1054
|
-
L.push('|
|
|
1053
|
+
L.push('| Action (intent) | How it runs | Where | Parameters | Type | Security |');
|
|
1055
1054
|
L.push('| --- | --- | --- | --- | --- | --- |');
|
|
1056
1055
|
for (const e of plan.auto) {
|
|
1057
1056
|
const expr = e.kind === 'bridge' ? e.bridge.invoke : e.callExpr;
|
|
@@ -1061,81 +1060,81 @@ function buildHandlerDoc(plan, ctx = {}) {
|
|
|
1061
1060
|
? e.manifestParams.map((p) => `${p.name}${p.required ? '*' : ''}`).join(', ')
|
|
1062
1061
|
: '—';
|
|
1063
1062
|
const kind = e.kind === 'bridge'
|
|
1064
|
-
? '
|
|
1065
|
-
: (e.source === 'ai' ? '
|
|
1063
|
+
? 'bridge (edits your component)'
|
|
1064
|
+
: (e.source === 'ai' ? 'module (AI)' : 'module (mechanical)');
|
|
1066
1065
|
L.push(`| \`${e.intent}\` | ${call} | ${where} | ${params} | ${kind} | ${confLabel(e)} |`);
|
|
1067
1066
|
}
|
|
1068
1067
|
L.push('');
|
|
1069
|
-
L.push('_`*` =
|
|
1070
|
-
'
|
|
1071
|
-
'
|
|
1068
|
+
L.push('_`*` = required parameter. Each call was proposed by the AI reading your ' +
|
|
1069
|
+
'code and **confirmed by the verifier** (the symbols exist). Parameters ' +
|
|
1070
|
+
'are mapped **by name**, never by position._');
|
|
1072
1071
|
L.push('');
|
|
1073
1072
|
}
|
|
1074
1073
|
|
|
1075
1074
|
if (review.length) {
|
|
1076
|
-
L.push(`##
|
|
1075
|
+
L.push(`## Actions that need REVIEW (${review.length})`);
|
|
1077
1076
|
L.push('');
|
|
1078
1077
|
L.push(
|
|
1079
|
-
'
|
|
1080
|
-
'
|
|
1081
|
-
'
|
|
1078
|
+
'For these actions, neither did the AI propose **verifiable** wiring nor did the ' +
|
|
1079
|
+
'mechanical fallback detector find the function safely. Wiring them wrong would be ' +
|
|
1080
|
+
'worse than leaving them unwired, so they are marked for review and we explain why:',
|
|
1082
1081
|
);
|
|
1083
1082
|
L.push('');
|
|
1084
1083
|
for (const e of review) {
|
|
1085
1084
|
L.push(`### \`${e.intent}\` — ${e.label}`);
|
|
1086
1085
|
L.push('');
|
|
1087
|
-
L.push(`-
|
|
1088
|
-
if (e.file) L.push(`-
|
|
1089
|
-
L.push(`-
|
|
1086
|
+
L.push(`- Expected handler: \`${e.handler}\``);
|
|
1087
|
+
if (e.file) L.push(`- Evidence file: \`${e.file}\``);
|
|
1088
|
+
L.push(`- Reason: ${e.reason}`);
|
|
1090
1089
|
L.push(
|
|
1091
|
-
`-
|
|
1092
|
-
'
|
|
1093
|
-
'`{ success: true }` (
|
|
1090
|
+
`- What to do: add a handler \`${e.handler}\` in \`${registryRel}\` (or in your ` +
|
|
1091
|
+
'own registry) that calls the real function and returns ' +
|
|
1092
|
+
'`{ success: true }` (or `{ success: false, message }`).',
|
|
1094
1093
|
);
|
|
1095
1094
|
L.push('');
|
|
1096
1095
|
}
|
|
1097
1096
|
}
|
|
1098
1097
|
|
|
1099
1098
|
const confirmed = plan.entries.filter((e) => e.requireConfirmation);
|
|
1100
|
-
L.push('##
|
|
1099
|
+
L.push('## Security');
|
|
1101
1100
|
L.push('');
|
|
1102
1101
|
L.push(
|
|
1103
|
-
'-
|
|
1104
|
-
'
|
|
1105
|
-
'
|
|
1106
|
-
'
|
|
1102
|
+
'- The generated wiring **only calls** your functions. It **never** decides whether a ' +
|
|
1103
|
+
'sensitive action runs: the Fiodos engine applies the manifest confirmations ' +
|
|
1104
|
+
'(`requireConfirmation` / `voiceConfirmation`) **before** invoking ' +
|
|
1105
|
+
'the handler. It is impossible for the auto-wiring to skip a confirmation.',
|
|
1107
1106
|
);
|
|
1108
1107
|
if (confirmed.length) {
|
|
1109
|
-
L.push('-
|
|
1108
|
+
L.push('- Actions protected by confirmation (the agent will ask to confirm before running them):');
|
|
1110
1109
|
for (const e of confirmed) {
|
|
1111
1110
|
L.push(` - \`${e.intent}\` — ${confLabel(e)}.`);
|
|
1112
1111
|
}
|
|
1113
1112
|
} else {
|
|
1114
|
-
L.push('-
|
|
1113
|
+
L.push('- None of the detected actions require confirmation in the manifest.');
|
|
1115
1114
|
}
|
|
1116
|
-
L.push('-
|
|
1117
|
-
L.push('
|
|
1115
|
+
L.push('- The wiring does not read, embed or expose secrets; it only references function');
|
|
1116
|
+
L.push(' names and the parameters the manifest already declares.');
|
|
1118
1117
|
L.push('');
|
|
1119
1118
|
|
|
1120
|
-
L.push('##
|
|
1119
|
+
L.push('## How to enable it');
|
|
1121
1120
|
L.push('');
|
|
1122
1121
|
if (mountSnippet) {
|
|
1123
|
-
L.push('
|
|
1122
|
+
L.push('Pass the generated registry to your `<FiodosAgent/>`:');
|
|
1124
1123
|
L.push('');
|
|
1125
1124
|
L.push('```tsx');
|
|
1126
1125
|
L.push(mountSnippet);
|
|
1127
1126
|
L.push('```');
|
|
1128
1127
|
} else {
|
|
1129
|
-
L.push('
|
|
1130
|
-
'`registries`
|
|
1128
|
+
L.push('Import `' + REGISTRY_EXPORT + '` from the generated file and pass it as ' +
|
|
1129
|
+
'`registries` to your `<FiodosAgent/>`.');
|
|
1131
1130
|
}
|
|
1132
1131
|
L.push('');
|
|
1133
|
-
L.push('##
|
|
1132
|
+
L.push('## If you said "no"');
|
|
1134
1133
|
L.push('');
|
|
1135
1134
|
L.push(
|
|
1136
|
-
'
|
|
1137
|
-
'
|
|
1138
|
-
'(`--wire-yes`
|
|
1135
|
+
'Your code was not touched. You can implement the wiring yourself following ' +
|
|
1136
|
+
'this document, or re-run the installer to accept the auto-wiring ' +
|
|
1137
|
+
'(`--wire-yes` to accept without prompting).',
|
|
1139
1138
|
);
|
|
1140
1139
|
L.push('');
|
|
1141
1140
|
return L.join('\n');
|
|
@@ -1365,7 +1364,7 @@ async function runAutoCorrectionLoop(ctx) {
|
|
|
1365
1364
|
} = ctx;
|
|
1366
1365
|
|
|
1367
1366
|
const log = (m) => console.error(`${tag} · ${dim || ''}${m}${reset || ''}`);
|
|
1368
|
-
log('
|
|
1367
|
+
log('Verifying and auto-correcting each action (may take a while)…');
|
|
1369
1368
|
const baseline = await testRunner(appRoot, { framework });
|
|
1370
1369
|
|
|
1371
1370
|
const ready = [];
|
|
@@ -1384,7 +1383,7 @@ async function runAutoCorrectionLoop(ctx) {
|
|
|
1384
1383
|
const { entry, review } = planOneAction({ appRoot, manifest, evidence, candidate: wiringMap[intent], intent, fyodosDirRel });
|
|
1385
1384
|
|
|
1386
1385
|
if (!entry) {
|
|
1387
|
-
lastError = (review && review.reason) || '
|
|
1386
|
+
lastError = (review && review.reason) || 'not mechanically verifiable';
|
|
1388
1387
|
history.push({ attempt, stage: 'mechanical', ok: false, error: lastError });
|
|
1389
1388
|
} else {
|
|
1390
1389
|
const iso = writeIsolated({ appRoot, fyodosDirAbs, entry, ts, esm, ext });
|
|
@@ -1393,10 +1392,10 @@ async function runAutoCorrectionLoop(ctx) {
|
|
|
1393
1392
|
try {
|
|
1394
1393
|
const after = await testRunner(appRoot, { framework });
|
|
1395
1394
|
const reg = compileRegressed(baseline.output, after.output, iso.touched);
|
|
1396
|
-
if (!reg.ok) { ok = false; lastError = `
|
|
1395
|
+
if (!reg.ok) { ok = false; lastError = `compilation: ${reg.newErrors.join(' | ').slice(0, 600)}`; }
|
|
1397
1396
|
if (ok) {
|
|
1398
1397
|
const eff = await probeEffect(appRoot, entry, { fyodosDirRel, framework, sources: files, sensitive: entry.requireConfirmation });
|
|
1399
|
-
if (eff.status === 'fail') { ok = false; stage = 'effect'; lastError = `
|
|
1398
|
+
if (eff.status === 'fail') { ok = false; stage = 'effect'; lastError = `effect: ${eff.detail}`; }
|
|
1400
1399
|
else {
|
|
1401
1400
|
const level = eff.status === 'effect-pass' ? 'effect'
|
|
1402
1401
|
: eff.status === 'unverifiable' ? 'unverifiable'
|
|
@@ -1405,7 +1404,7 @@ async function runAutoCorrectionLoop(ctx) {
|
|
|
1405
1404
|
}
|
|
1406
1405
|
}
|
|
1407
1406
|
} catch (err) {
|
|
1408
|
-
ok = false; lastError = `
|
|
1407
|
+
ok = false; lastError = `verification threw: ${err && err.message}`;
|
|
1409
1408
|
} finally {
|
|
1410
1409
|
revertAll(iso.backups, iso.generated);
|
|
1411
1410
|
}
|
|
@@ -1414,14 +1413,14 @@ async function runAutoCorrectionLoop(ctx) {
|
|
|
1414
1413
|
}
|
|
1415
1414
|
|
|
1416
1415
|
if (attempt >= maxAttempts || typeof corrector !== 'function') break;
|
|
1417
|
-
log(`
|
|
1416
|
+
log(`Action '${intent}' failed (attempt ${attempt}): ${lastError.slice(0, 120)} → asking the AI to correct it…`);
|
|
1418
1417
|
try {
|
|
1419
1418
|
const relevantFiles = pickRelevantFiles(files, evidence, wiringMap[intent], intent);
|
|
1420
1419
|
const corrected = await corrector({ action, intent, evidence: (evidence && evidence.actions) || {}, relevantFiles, prevWiring: wiringMap[intent], errorText: lastError, attempt });
|
|
1421
1420
|
if (corrected && corrected[intent]) wiringMap[intent] = corrected[intent];
|
|
1422
|
-
else { history.push({ attempt, stage: 'correct', ok: false, error: '
|
|
1421
|
+
else { history.push({ attempt, stage: 'correct', ok: false, error: 'the AI did not return corrected wiring' }); break; }
|
|
1423
1422
|
} catch (err) {
|
|
1424
|
-
history.push({ attempt, stage: 'correct', ok: false, error: `corrector
|
|
1423
|
+
history.push({ attempt, stage: 'correct', ok: false, error: `corrector failed: ${err && err.message}` });
|
|
1425
1424
|
break;
|
|
1426
1425
|
}
|
|
1427
1426
|
}
|
|
@@ -1429,11 +1428,11 @@ async function runAutoCorrectionLoop(ctx) {
|
|
|
1429
1428
|
if (resolved) {
|
|
1430
1429
|
ready.push(resolved.entry);
|
|
1431
1430
|
attemptsLog.push({ intent, attempts: attempt, status: 'ready', level: resolved.level, detail: resolved.detail, history });
|
|
1432
|
-
log(`✓ '${intent}'
|
|
1431
|
+
log(`✓ '${intent}' verified in ${attempt} attempt(s) [${resolved.level === 'effect' ? 'real effect' : 'compilation'}].`);
|
|
1433
1432
|
} else {
|
|
1434
1433
|
dropped.push({ ...action, intent, reason: lastError, attempts: attempt, handler: action.handler || intent, label: action.label || intent, manifestParams: Object.entries(action.parameters || {}).map(([name, spec]) => ({ name, required: spec && spec.required === true })) });
|
|
1435
1434
|
attemptsLog.push({ intent, attempts: attempt, status: 'dropped', error: lastError, history });
|
|
1436
|
-
log(`✗ '${intent}'
|
|
1435
|
+
log(`✗ '${intent}' could not be wired after ${attempt} attempt(s): ${lastError.slice(0, 140)}`);
|
|
1437
1436
|
}
|
|
1438
1437
|
}
|
|
1439
1438
|
|
|
@@ -1518,31 +1517,31 @@ async function wireHandlers(appRoot, opts = {}) {
|
|
|
1518
1517
|
const loopEnabled = typeof corrector === 'function' && runTest && testRunner;
|
|
1519
1518
|
if (!plan.auto.length && !loopEnabled) {
|
|
1520
1519
|
console.error(
|
|
1521
|
-
`\n${tag} · ${dim || ''}
|
|
1522
|
-
`${plan.manual.length}
|
|
1520
|
+
`\n${tag} · ${dim || ''}Prepared the action wiring, but none of the ` +
|
|
1521
|
+
`${plan.manual.length} action(s) can be connected with confidence.${reset || ''}`,
|
|
1523
1522
|
);
|
|
1524
1523
|
console.error(
|
|
1525
|
-
`${tag} ·
|
|
1524
|
+
`${tag} · See what is needed and how to do it by hand in: ${docRel}`,
|
|
1526
1525
|
);
|
|
1527
1526
|
return { status: 'manual-only', docPath: docAbs, autoCount: 0, manualCount: plan.manual.length, plan };
|
|
1528
1527
|
}
|
|
1529
1528
|
|
|
1530
1529
|
// 3) Second, separate confirmation (after the orb's), referencing the doc.
|
|
1531
1530
|
const touchNote = componentEdits.length
|
|
1532
|
-
? `
|
|
1531
|
+
? ` ${componentEdits.length} file(s) of YOURS will be edited (with reversible markers).`
|
|
1533
1532
|
: '';
|
|
1534
1533
|
const prompt =
|
|
1535
|
-
`\n${tag} ·
|
|
1536
|
-
`
|
|
1537
|
-
`${tag} ·
|
|
1534
|
+
`\n${tag} · The action wiring that lets the agent run your functions is ` +
|
|
1535
|
+
`ready (${plan.auto.length} automatic, ${plan.manual.length} for review).${touchNote}\n` +
|
|
1536
|
+
`${tag} · Review the exact changes that will be made to your code in:\n` +
|
|
1538
1537
|
` ${docRel}\n` +
|
|
1539
|
-
`${tag} ·
|
|
1538
|
+
`${tag} · Apply these changes after reviewing? [yes/no] `;
|
|
1540
1539
|
|
|
1541
1540
|
if (!assumeYes && !process.stdin.isTTY) {
|
|
1542
1541
|
console.error(prompt.trimEnd());
|
|
1543
1542
|
console.error(
|
|
1544
|
-
`${tag} · ${dim || ''}terminal
|
|
1545
|
-
`
|
|
1543
|
+
`${tag} · ${dim || ''}Non-interactive terminal: leaving your code untouched. The document ` +
|
|
1544
|
+
`is at ${docRel}. Re-run with --wire-yes to apply it.${reset || ''}`,
|
|
1546
1545
|
);
|
|
1547
1546
|
return { status: 'declined-noninteractive', docPath: docAbs, autoCount: plan.auto.length, manualCount: plan.manual.length, plan };
|
|
1548
1547
|
}
|
|
@@ -1550,8 +1549,8 @@ async function wireHandlers(appRoot, opts = {}) {
|
|
|
1550
1549
|
const yes = assumeYes || (await askYesNo(prompt));
|
|
1551
1550
|
if (!yes) {
|
|
1552
1551
|
console.error(
|
|
1553
|
-
`${tag} · ${dim || ''}
|
|
1554
|
-
`
|
|
1552
|
+
`${tag} · ${dim || ''}Leaving your code untouched. The document stays at ${docRel} ` +
|
|
1553
|
+
`in case you want to wire it by hand or re-run with --wire-yes.${reset || ''}`,
|
|
1555
1554
|
);
|
|
1556
1555
|
return { status: 'declined', docPath: docAbs, autoCount: plan.auto.length, manualCount: plan.manual.length, plan };
|
|
1557
1556
|
}
|
|
@@ -1561,7 +1560,7 @@ async function wireHandlers(appRoot, opts = {}) {
|
|
|
1561
1560
|
// (don't revert a good wiring over a pre-existing failure).
|
|
1562
1561
|
let baseline = null;
|
|
1563
1562
|
if (runTest && testRunner) {
|
|
1564
|
-
console.error(`${tag} · ${dim || ''}
|
|
1563
|
+
console.error(`${tag} · ${dim || ''}Checking the build baseline BEFORE wiring…${reset || ''}`);
|
|
1565
1564
|
try {
|
|
1566
1565
|
baseline = await testRunner(appRoot, { framework });
|
|
1567
1566
|
} catch (err) {
|
|
@@ -1587,7 +1586,7 @@ async function wireHandlers(appRoot, opts = {}) {
|
|
|
1587
1586
|
const droppedReview = loop.dropped.map((d) => ({
|
|
1588
1587
|
...d,
|
|
1589
1588
|
confidence: 'review',
|
|
1590
|
-
reason: `
|
|
1589
|
+
reason: `could not be wired automatically after ${d.attempts} attempt(s): ${d.reason}`,
|
|
1591
1590
|
}));
|
|
1592
1591
|
const finalPlan = {
|
|
1593
1592
|
auto: finalAuto, review: droppedReview, manual: droppedReview,
|
|
@@ -1693,7 +1692,7 @@ async function wireHandlers(appRoot, opts = {}) {
|
|
|
1693
1692
|
if (baseline && baseline.ok === false && baseline.stage !== 'skipped-no-deps') {
|
|
1694
1693
|
return { status: 'applied-untested', ...baseResult, test: baseline, preexistingFailure: true };
|
|
1695
1694
|
}
|
|
1696
|
-
console.error(`${tag} · ${dim || ''}
|
|
1695
|
+
console.error(`${tag} · ${dim || ''}Checking the app still builds after wiring…${reset || ''}`);
|
|
1697
1696
|
let test;
|
|
1698
1697
|
try {
|
|
1699
1698
|
test = await testRunner(appRoot, { framework });
|
|
@@ -1719,45 +1718,45 @@ function reportHandlerResult(result, colors = {}) {
|
|
|
1719
1718
|
case 'applied':
|
|
1720
1719
|
case 'applied-untested': {
|
|
1721
1720
|
const rel = result.registryRel || result.registryFile;
|
|
1722
|
-
console.error(`${tag} · ✓
|
|
1721
|
+
console.error(`${tag} · ✓ Handler wiring applied → ${rel} (${result.autoCount} action(s)).`);
|
|
1723
1722
|
if (Array.isArray(result.verification) && result.verification.length) {
|
|
1724
1723
|
const ready = result.verification.filter((v) => v.status === 'ready');
|
|
1725
1724
|
const corrected = ready.filter((v) => v.attempts > 1).length;
|
|
1726
1725
|
const firstTry = ready.length - corrected;
|
|
1727
1726
|
const eff = ready.filter((v) => v.level === 'effect').length;
|
|
1728
|
-
console.error(`${tag} · ${dim || ''}
|
|
1727
|
+
console.error(`${tag} · ${dim || ''}Verification: ${ready.length} action(s) ready (${firstTry} on first try, ${corrected} after auto-correction, ${eff} with real effect confirmed).${reset || ''}`);
|
|
1729
1728
|
}
|
|
1730
1729
|
if (result.editedFiles && result.editedFiles.length) {
|
|
1731
|
-
console.error(`${tag} · ${dim || ''}
|
|
1730
|
+
console.error(`${tag} · ${dim || ''}Bridges added (reversible) in: ${result.editedFiles.join(', ')}.${reset || ''}`);
|
|
1732
1731
|
}
|
|
1733
1732
|
if (Array.isArray(result.dropped) && result.dropped.length) {
|
|
1734
|
-
console.error(`${tag} · ${dim || ''}${result.dropped.length}
|
|
1733
|
+
console.error(`${tag} · ${dim || ''}${result.dropped.length} action(s) could NOT be wired after retries (reverted, see the document): ${result.dropped.map((d) => d.intent).join(', ')}.${reset || ''}`);
|
|
1735
1734
|
} else if (result.manualCount) {
|
|
1736
|
-
console.error(`${tag} · ${dim || ''}${result.manualCount}
|
|
1735
|
+
console.error(`${tag} · ${dim || ''}${result.manualCount} action(s) left for review (see the document).${reset || ''}`);
|
|
1737
1736
|
}
|
|
1738
1737
|
if (result.test && result.test.ok) {
|
|
1739
|
-
console.error(`${tag} · ✓
|
|
1738
|
+
console.error(`${tag} · ✓ Post-wiring build check OK (${result.test.stage}).`);
|
|
1740
1739
|
} else if (result.preexistingFailure) {
|
|
1741
|
-
console.error(`${tag} · ${dim || ''}
|
|
1740
|
+
console.error(`${tag} · ${dim || ''}Note: your app was ALREADY not building BEFORE wiring (${result.test && result.test.stage}); not caused by Fiodos, so the wiring is kept but cannot be build-verified.${reset || ''}`);
|
|
1742
1741
|
} else if (result.status === 'applied-untested') {
|
|
1743
|
-
console.error(`${tag} · ${dim || ''}
|
|
1742
|
+
console.error(`${tag} · ${dim || ''}Note: could not verify the build (${result.test && result.test.stage}); please check it.${reset || ''}`);
|
|
1744
1743
|
}
|
|
1745
|
-
console.error(`${tag} · ${dim || ''}
|
|
1744
|
+
console.error(`${tag} · ${dim || ''}Enable it by passing registries={fyodosGeneratedRegistries} to <FiodosAgent/> (snippet in the document).${reset || ''}`);
|
|
1746
1745
|
break;
|
|
1747
1746
|
}
|
|
1748
1747
|
case 'reverted':
|
|
1749
|
-
console.error(`${tag} · ✗
|
|
1750
|
-
console.error(`${tag} · ${dim || ''}
|
|
1748
|
+
console.error(`${tag} · ✗ Post-wiring build check failed (${result.test && result.test.stage}) — reverted all changes, your app is untouched.`);
|
|
1749
|
+
console.error(`${tag} · ${dim || ''}Details in the document; nothing was modified in your code.${reset || ''}`);
|
|
1751
1750
|
break;
|
|
1752
1751
|
case 'manual-only':
|
|
1753
|
-
console.error(`${tag} · ${dim || ''}
|
|
1752
|
+
console.error(`${tag} · ${dim || ''}Wiring documented; apply it by hand following ${result.docPath}.${reset || ''}`);
|
|
1754
1753
|
break;
|
|
1755
1754
|
case 'declined':
|
|
1756
1755
|
case 'declined-noninteractive':
|
|
1757
1756
|
// already reported inline
|
|
1758
1757
|
break;
|
|
1759
1758
|
case 'failed':
|
|
1760
|
-
console.error(`${tag} · ✗
|
|
1759
|
+
console.error(`${tag} · ✗ Could not write the handler registry: ${result.error && result.error.message}`);
|
|
1761
1760
|
break;
|
|
1762
1761
|
case 'no-actions':
|
|
1763
1762
|
case 'skipped':
|