@shiftleftpt/sbd-toe-mcp 0.20.0-beta.20 → 0.20.0-beta.22
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/assets/agent-guide.md +33 -1
- package/dist/index.js +64 -18
- package/dist/index.js.map +1 -1
- package/dist/serving/activation-vocabulary.d.ts +99 -0
- package/dist/serving/activation-vocabulary.js +165 -0
- package/dist/serving/activation-vocabulary.js.map +1 -0
- package/dist/serving/selection.d.ts +71 -2
- package/dist/serving/selection.js +237 -15
- package/dist/serving/selection.js.map +1 -1
- package/dist/tools/map-review-scope.d.ts +6 -0
- package/dist/tools/map-review-scope.js +1 -1
- package/dist/tools/map-review-scope.js.map +1 -1
- package/dist/tools/prepare-codegen-context.d.ts +40 -4
- package/dist/tools/prepare-codegen-context.js +194 -119
- package/dist/tools/prepare-codegen-context.js.map +1 -1
- package/dist/tools/select-requirements.d.ts +22 -0
- package/dist/tools/select-requirements.js +57 -5
- package/dist/tools/select-requirements.js.map +1 -1
- package/package.json +1 -1
|
@@ -33,7 +33,7 @@ import { servedKgReleaseTag } from "../version-info.js";
|
|
|
33
33
|
import { expandQueryWithAliases } from "../backend/semantic-index-gateway.js";
|
|
34
34
|
import { requirementCategoryOf } from "../serving/requirement-id.js";
|
|
35
35
|
import { prepareCodegenAffordances } from "../serving/affordances.js";
|
|
36
|
-
import { runSelectionWithActivation } from "../serving/selection.js";
|
|
36
|
+
import { runSelectionWithActivation, normalizeDeclaredTechnologies, stackTokensFromVocabulary } from "../serving/selection.js";
|
|
37
37
|
import { REQUIREMENT_CEILING_BY_DETAIL, COST_PER_REQ_TK, BASE_TK, PAYLOAD_PROMISE_TK, projectedCostTk } from "../serving/payload-ceilings.js";
|
|
38
38
|
const EVIDENCE_PATTERN_CAP = 25;
|
|
39
39
|
/**
|
|
@@ -275,7 +275,24 @@ const TASK_TERM_TO_CONCERNS = [
|
|
|
275
275
|
* categories. Keys are concerns added by this module. The categories map to
|
|
276
276
|
* existing runtime v0 categories so the v0 requirement filter still works.
|
|
277
277
|
*/
|
|
278
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Declared context activators (G-mp1a/D3). Module scope + exported since
|
|
280
|
+
* 0.20.0-beta.21 (declarative-first): `sbd://toe/activation-vocabulary` publishes
|
|
281
|
+
* them — the vocabulary IS the contract, so it is derived from these tables and
|
|
282
|
+
* never restated by hand.
|
|
283
|
+
*/
|
|
284
|
+
export const EXPOSURE_CONCERNS = {
|
|
285
|
+
internal: ["auth", "logging"],
|
|
286
|
+
authenticated: ["auth", "logging"],
|
|
287
|
+
public: ["auth", "logging", "api", "validation", "architecture"]
|
|
288
|
+
};
|
|
289
|
+
export const SENSITIVITY_CONCERNS = {
|
|
290
|
+
// v1.8.0: personal/regulated data now also activates the PRI catalogue (privacy).
|
|
291
|
+
personal: ["encryption", "validation", "logging", "privacy"],
|
|
292
|
+
regulated: ["encryption", "validation", "logging", "privacy"],
|
|
293
|
+
secrets: ["secrets"]
|
|
294
|
+
};
|
|
295
|
+
export const CONCERN_TO_V0_CATEGORIES_SUPPLEMENT = {
|
|
279
296
|
// Existing concerns intentionally left empty — fall back to ontology.concernsMap.
|
|
280
297
|
auth: [],
|
|
281
298
|
logging: [],
|
|
@@ -589,7 +606,8 @@ function recordActivation(trace, concerns, scores, rejected, entry, targetConcer
|
|
|
589
606
|
trace.push(entry);
|
|
590
607
|
}
|
|
591
608
|
}
|
|
592
|
-
export function activate(input) {
|
|
609
|
+
export function activate(input, options = {}) {
|
|
610
|
+
const declaredOnly = options.declaredOnly ?? false;
|
|
593
611
|
const trace = [];
|
|
594
612
|
const rejected = [];
|
|
595
613
|
const notes = [];
|
|
@@ -616,110 +634,118 @@ export function activate(input) {
|
|
|
616
634
|
reason: `Concern '${unknown}' is not in the WP5/WP6 lexicon (${VALID_CONCERNS.join(", ")}).`
|
|
617
635
|
});
|
|
618
636
|
}
|
|
619
|
-
//
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
//
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
recordActivation(trace, concerns, concernScores, rejected, {
|
|
631
|
-
source: "compound_term",
|
|
632
|
-
produced: concern,
|
|
633
|
-
trigger: phrase,
|
|
634
|
-
score: 0.7,
|
|
635
|
-
confidence: "semantic",
|
|
636
|
-
reason: `Compound phrase '${phrase}' activates ${concern}.`
|
|
637
|
-
}, concern, { capDuplicates: true });
|
|
637
|
+
// 0.20.0-beta.21 — DECLARATIVO PRIMEIRO: tudo o que se segue até ao bloco 4 é
|
|
638
|
+
// INFERÊNCIA SOBRE PROSA/PATHS (termos da tarefa, aliases, compostos, homónimo
|
|
639
|
+
// da imagem, intenções, e as heurísticas de NOME de ficheiro). No caminho
|
|
640
|
+
// declarativo (default nesta linha) NÃO corre: o servidor responde ao que lhe
|
|
641
|
+
// declararam. Fica disponível em mode="discover" — instrumento de investigação
|
|
642
|
+
// (oráculo histórico + estudo de paráfrase), marcado exploratório na resposta.
|
|
643
|
+
if (!declaredOnly) {
|
|
644
|
+
// 2) Alias expansion + direct task-term matches.
|
|
645
|
+
const { expanded, appliedAliases } = expandTaskText(input.taskLower);
|
|
646
|
+
for (const alias of appliedAliases) {
|
|
647
|
+
notes.push(`alias_expansion: '${alias.pt}' -> [${alias.en.join(", ")}]`);
|
|
638
648
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
reason: viaAlias
|
|
655
|
-
? `Task text matches '${term}' after PT/EN alias expansion.`
|
|
656
|
-
: `Task text contains '${term}'.`
|
|
657
|
-
}, concern, { capDuplicates: true });
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
// 2b-bis) R-image (vaga v1.8.0, 2026-08-31): "image"/"imagem" é homónimo —
|
|
661
|
-
// imagem de container vs ficheiro de imagem (finding do replay DualGauge).
|
|
662
|
-
// Desambiguação DECLARADA por contexto: image+docker/registry/container → sentido
|
|
663
|
-
// container (deployment/distribution); image+file/upload/photo → FIL (files);
|
|
664
|
-
// ambos os contextos → ambos; nenhum → sentido histórico (deployment/distribution).
|
|
665
|
-
if (taskMatchesKeyword(expanded, "image")) {
|
|
666
|
-
const containerCtx = /docker|registry|container|kubernetes|k8s|\boci\b/.test(expanded);
|
|
667
|
-
const fileCtx = /upload|file|photo|picture|png|jpe?g|gif|avatar|galeria|gallery|perfil|profile/.test(expanded);
|
|
668
|
-
const senses = [];
|
|
669
|
-
if (fileCtx) {
|
|
670
|
-
senses.push({ concern: "files", reason: "R-image: 'image' em contexto file/upload/photo → ficheiro de imagem (FIL)" });
|
|
649
|
+
// 2a) Compound phrases (run first — they encode canonical multi-domain asks).
|
|
650
|
+
// Whole-word match prevents accidental hits inside larger tokens.
|
|
651
|
+
for (const [phrase, mapped] of COMPOUND_TERM_TO_CONCERNS) {
|
|
652
|
+
if (!taskMatchesKeyword(expanded, phrase))
|
|
653
|
+
continue;
|
|
654
|
+
for (const concern of mapped) {
|
|
655
|
+
recordActivation(trace, concerns, concernScores, rejected, {
|
|
656
|
+
source: "compound_term",
|
|
657
|
+
produced: concern,
|
|
658
|
+
trigger: phrase,
|
|
659
|
+
score: 0.7,
|
|
660
|
+
confidence: "semantic",
|
|
661
|
+
reason: `Compound phrase '${phrase}' activates ${concern}.`
|
|
662
|
+
}, concern, { capDuplicates: true });
|
|
663
|
+
}
|
|
671
664
|
}
|
|
672
|
-
|
|
673
|
-
|
|
665
|
+
// 2b) Single-token task terms. Whole-word matching prevents false positives
|
|
666
|
+
// like `test` inside `latest` or `log` inside `logical`.
|
|
667
|
+
for (const [term, mapped] of TASK_TERM_TO_CONCERNS) {
|
|
668
|
+
if (!taskMatchesKeyword(expanded, term))
|
|
669
|
+
continue;
|
|
670
|
+
const viaAlias = !taskMatchesKeyword(input.taskLower, term) &&
|
|
671
|
+
appliedAliases.some((alias) => alias.en.includes(term));
|
|
672
|
+
for (const concern of mapped) {
|
|
673
|
+
recordActivation(trace, concerns, concernScores, rejected, {
|
|
674
|
+
source: viaAlias ? "alias_expansion" : "task_term",
|
|
675
|
+
produced: concern,
|
|
676
|
+
trigger: term,
|
|
677
|
+
score: viaAlias ? 0.6 : 0.8,
|
|
678
|
+
confidence: viaAlias ? "semantic" : "deterministic",
|
|
679
|
+
reason: viaAlias
|
|
680
|
+
? `Task text matches '${term}' after PT/EN alias expansion.`
|
|
681
|
+
: `Task text contains '${term}'.`
|
|
682
|
+
}, concern, { capDuplicates: true });
|
|
683
|
+
}
|
|
674
684
|
}
|
|
675
|
-
|
|
676
|
-
|
|
685
|
+
// 2b-bis) R-image (vaga v1.8.0, 2026-08-31): "image"/"imagem" é homónimo —
|
|
686
|
+
// imagem de container vs ficheiro de imagem (finding do replay DualGauge).
|
|
687
|
+
// Desambiguação DECLARADA por contexto: image+docker/registry/container → sentido
|
|
688
|
+
// container (deployment/distribution); image+file/upload/photo → FIL (files);
|
|
689
|
+
// ambos os contextos → ambos; nenhum → sentido histórico (deployment/distribution).
|
|
690
|
+
if (taskMatchesKeyword(expanded, "image")) {
|
|
691
|
+
const containerCtx = /docker|registry|container|kubernetes|k8s|\boci\b/.test(expanded);
|
|
692
|
+
const fileCtx = /upload|file|photo|picture|png|jpe?g|gif|avatar|galeria|gallery|perfil|profile/.test(expanded);
|
|
693
|
+
const senses = [];
|
|
694
|
+
if (fileCtx) {
|
|
695
|
+
senses.push({ concern: "files", reason: "R-image: 'image' em contexto file/upload/photo → ficheiro de imagem (FIL)" });
|
|
696
|
+
}
|
|
697
|
+
if (containerCtx || !fileCtx) {
|
|
698
|
+
senses.push({ concern: "deployment", reason: containerCtx ? "R-image: 'image' em contexto docker/registry/container → imagem de container" : "R-image: 'image' sem contexto discriminante → sentido histórico (deployment)" }, { concern: "distribution", reason: containerCtx ? "R-image: 'image' em contexto docker/registry/container → distribuição de imagem" : "R-image: 'image' sem contexto discriminante → sentido histórico (distribution)" });
|
|
699
|
+
}
|
|
700
|
+
for (const { concern, reason } of senses) {
|
|
701
|
+
recordActivation(trace, concerns, concernScores, rejected, { source: "task_term", produced: concern, trigger: "image", score: 0.8, confidence: "deterministic", reason }, concern, { capDuplicates: true });
|
|
702
|
+
}
|
|
677
703
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
}
|
|
704
|
+
// 2c) Whole-word intent classification (codegen-specific; stricter than the
|
|
705
|
+
// gateway's substring matcher to avoid PT/EN false positives).
|
|
706
|
+
for (const intentEntry of CODEGEN_INTENTS) {
|
|
707
|
+
const matchedKeyword = intentEntry.keywords.find((keyword) => taskMatchesKeyword(expanded, keyword));
|
|
708
|
+
if (!matchedKeyword)
|
|
709
|
+
continue;
|
|
710
|
+
for (const concern of intentEntry.concerns) {
|
|
711
|
+
recordActivation(trace, concerns, concernScores, rejected, {
|
|
712
|
+
source: "intent_keyword",
|
|
713
|
+
produced: concern,
|
|
714
|
+
trigger: intentEntry.intent,
|
|
715
|
+
score: 0.5,
|
|
716
|
+
confidence: "semantic",
|
|
717
|
+
reason: `Intent '${intentEntry.intent}' matched keyword '${matchedKeyword}'.`
|
|
718
|
+
}, concern, { capDuplicates: false });
|
|
719
|
+
}
|
|
694
720
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
fileHits
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
721
|
+
// 3) Changed-file path heuristics.
|
|
722
|
+
for (const file of input.changed_files) {
|
|
723
|
+
const lower = file.toLowerCase();
|
|
724
|
+
const fileHits = [];
|
|
725
|
+
if (/route|router|controller|handler|endpoint/.test(lower))
|
|
726
|
+
fileHits.push("api");
|
|
727
|
+
if (/auth|session|jwt|login/.test(lower))
|
|
728
|
+
fileHits.push("auth");
|
|
729
|
+
if (/log|logger/.test(lower))
|
|
730
|
+
fileHits.push("logging");
|
|
731
|
+
if (/config|env|settings/.test(lower))
|
|
732
|
+
fileHits.push("config");
|
|
733
|
+
if (/secret|credential/.test(lower))
|
|
734
|
+
fileHits.push("secrets");
|
|
735
|
+
if (/test|spec/.test(lower))
|
|
736
|
+
fileHits.push("testing");
|
|
737
|
+
if (/dockerfile|docker-compose|k8s|kubernetes|terraform/.test(lower))
|
|
738
|
+
fileHits.push("deployment");
|
|
739
|
+
for (const concern of fileHits) {
|
|
740
|
+
recordActivation(trace, concerns, concernScores, rejected, {
|
|
741
|
+
source: "changed_file",
|
|
742
|
+
produced: concern,
|
|
743
|
+
trigger: file,
|
|
744
|
+
score: 0.5,
|
|
745
|
+
confidence: "semantic",
|
|
746
|
+
reason: `Changed file path matches '${concern}' heuristics.`
|
|
747
|
+
}, concern, { capDuplicates: true });
|
|
748
|
+
}
|
|
723
749
|
}
|
|
724
750
|
}
|
|
725
751
|
// 4) Slice families (ranked by max contributing concern score).
|
|
@@ -734,7 +760,11 @@ export function activate(input) {
|
|
|
734
760
|
sliceFamilyScores.set(family, score);
|
|
735
761
|
}
|
|
736
762
|
trace.push({
|
|
737
|
-
|
|
763
|
+
// 0.20.0-beta.22 (P2-A): isto NUNCA foi um termo da tarefa — é o mapeamento
|
|
764
|
+
// determinístico concern → slice family. A etiqueta `task_term` era órfã do
|
|
765
|
+
// motor lexical e aparecia mesmo com `task` vazio. Em `discover` o task_term
|
|
766
|
+
// legítimo (casamento de palavras) mantém-se; aqui a fonte diz o que é.
|
|
767
|
+
source: declaredOnly ? "concern_slice_mapping" : "task_term",
|
|
738
768
|
produced: family,
|
|
739
769
|
trigger: concern,
|
|
740
770
|
score,
|
|
@@ -747,11 +777,6 @@ export function activate(input) {
|
|
|
747
777
|
// rule (each with its own trace source), because the reference selection
|
|
748
778
|
// semantics says an authenticated/public surface must be auditable and a
|
|
749
779
|
// personal/regulated data context must carry crypto+masking+validation.
|
|
750
|
-
const EXPOSURE_CONCERNS = {
|
|
751
|
-
internal: ["auth", "logging"],
|
|
752
|
-
authenticated: ["auth", "logging"],
|
|
753
|
-
public: ["auth", "logging", "api", "validation", "architecture"]
|
|
754
|
-
};
|
|
755
780
|
if (input.exposure && EXPOSURE_CONCERNS[input.exposure]) {
|
|
756
781
|
for (const concern of EXPOSURE_CONCERNS[input.exposure] ?? []) {
|
|
757
782
|
recordActivation(trace, concerns, concernScores, rejected, {
|
|
@@ -764,12 +789,6 @@ export function activate(input) {
|
|
|
764
789
|
}, concern, { capDuplicates: true });
|
|
765
790
|
}
|
|
766
791
|
}
|
|
767
|
-
const SENSITIVITY_CONCERNS = {
|
|
768
|
-
// v1.8.0: personal/regulated data now also activates the PRI catalogue (privacy).
|
|
769
|
-
personal: ["encryption", "validation", "logging", "privacy"],
|
|
770
|
-
regulated: ["encryption", "validation", "logging", "privacy"],
|
|
771
|
-
secrets: ["secrets"]
|
|
772
|
-
};
|
|
773
792
|
if (input.data_sensitivity && SENSITIVITY_CONCERNS[input.data_sensitivity]) {
|
|
774
793
|
for (const concern of SENSITIVITY_CONCERNS[input.data_sensitivity] ?? []) {
|
|
775
794
|
recordActivation(trace, concerns, concernScores, rejected, {
|
|
@@ -782,6 +801,22 @@ export function activate(input) {
|
|
|
782
801
|
}, concern, { capDuplicates: true });
|
|
783
802
|
}
|
|
784
803
|
}
|
|
804
|
+
// 5-pre) 0.20.0-beta.22 (P1-D): o `stack` é a ÚNICA leitura de texto que resta no
|
|
805
|
+
// caminho declarativo (token EXACTO de um conjunto fechado — normalizar o declarado).
|
|
806
|
+
// Deixava de fora o rasto: os capítulos apareciam sem que o auditor pudesse ver
|
|
807
|
+
// porquê. Agora cada token reconhecido emite a sua entrada.
|
|
808
|
+
if (declaredOnly && input.stack) {
|
|
809
|
+
for (const token of stackTokensFromVocabulary(input.stack)) {
|
|
810
|
+
trace.push({
|
|
811
|
+
source: "stack_token",
|
|
812
|
+
produced: token,
|
|
813
|
+
trigger: "stack",
|
|
814
|
+
score: 0.9,
|
|
815
|
+
confidence: "deterministic",
|
|
816
|
+
reason: `token exacto de \`technologies\` encontrado em \`stack\`: '${token}' (normalização de valor declarado; o texto livre à volta é ignorado)`
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
785
820
|
// 5) Risk level (informational trace entry, no concern activation).
|
|
786
821
|
if (input.risk_level) {
|
|
787
822
|
trace.push({
|
|
@@ -877,13 +912,19 @@ function gateBeforeActivation(input) {
|
|
|
877
912
|
}
|
|
878
913
|
function gateAfterActivation(args) {
|
|
879
914
|
const { input, activation, estimatedRequirements } = args;
|
|
915
|
+
const declaredSurfaces = args.declaredSurfaces === true;
|
|
880
916
|
const reasons = [];
|
|
881
917
|
const suggestions = [];
|
|
882
918
|
// P3 do ciclo MP1 (2026-08-31): o gate conta SUPERFÍCIES (famílias dos concerns
|
|
883
919
|
// primários de cada sinal), não o total de famílias activadas — concerns de
|
|
884
920
|
// suporte de um mesmo sinal (mtls→secrets, mensageria→logging) não pedem
|
|
885
921
|
// decomposição. GC-10 é o caso de referência: 1 integração legítima.
|
|
886
|
-
|
|
922
|
+
// 0.20.0-beta.21 («declarativo primeiro»): o gate de decomposição nasceu para travar
|
|
923
|
+
// pedidos VAGOS cuja prosa activava meio catálogo. Quando as famílias vêm de
|
|
924
|
+
// DECLARAÇÕES explícitas, bloquear contradiz o contrato — o chamador não foi vago,
|
|
925
|
+
// foi preciso. O guarda do tamanho da resposta passa a ser (e já era) o tecto de
|
|
926
|
+
// requisitos por detail (0.19.4). Em `discover` a regra mantém-se tal e qual.
|
|
927
|
+
if (!declaredSurfaces && activation.decompositionFamilies.length > 3) {
|
|
887
928
|
reasons.push(`Pedido activa ${activation.decompositionFamilies.length} superfícies (famílias primárias: ${activation.decompositionFamilies.join(", ")}) — máximo recomendado: 3. Concerns de suporte do mesmo sinal não contam.`);
|
|
888
929
|
suggestions.push("Reparte por slice family. Cada PR/PR-step deve ficar em 1–3 slices.");
|
|
889
930
|
}
|
|
@@ -896,7 +937,7 @@ function gateAfterActivation(args) {
|
|
|
896
937
|
// D1 (G-mp1a): with the requirement-count cap gone, the no-signal guard is the
|
|
897
938
|
// vagueness catch-all. The informational risk_level trace entry must not defeat
|
|
898
939
|
// it — only real signals (concerns) count.
|
|
899
|
-
if (activation.concerns.length === 0 && input.tokenCount >= 4) {
|
|
940
|
+
if (!declaredSurfaces && activation.concerns.length === 0 && input.tokenCount >= 4) {
|
|
900
941
|
return {
|
|
901
942
|
status: "needs_clarification",
|
|
902
943
|
reasons: [
|
|
@@ -2153,7 +2194,22 @@ function prepareCodegenContextCore(raw) {
|
|
|
2153
2194
|
if (preGate && preGate.status !== "ready_for_codegen") {
|
|
2154
2195
|
return blocked(input, raw, preGate.status, preGate.reasons, preGate.suggestions, [], { rejected: [], notes: [] });
|
|
2155
2196
|
}
|
|
2156
|
-
|
|
2197
|
+
// 0.20.0-beta.21 — «declarativo primeiro»: por defeito o prepare responde ao
|
|
2198
|
+
// DECLARADO. Sem nenhuma declaração não adivinha a partir do `task`: devolve
|
|
2199
|
+
// needs_input com o vocabulário e a receita (o gateway semântico e o
|
|
2200
|
+
// classificador de intenção vivem no bloco lexical, que aqui não corre).
|
|
2201
|
+
const selectionMode = raw.selection_mode === "discover" ? "discover" : "declarative";
|
|
2202
|
+
const declaredTechnologies = normalizeDeclaredTechnologies(Array.isArray(raw.technologies) ? raw.technologies.filter((t) => typeof t === "string") : [], input.stack);
|
|
2203
|
+
const declarativeSelection = selectionMode !== "discover";
|
|
2204
|
+
const hasDeclaredActivator = input.concerns.length > 0 ||
|
|
2205
|
+
input.exposure !== undefined ||
|
|
2206
|
+
input.data_sensitivity !== undefined ||
|
|
2207
|
+
input.changed_files.length > 0 ||
|
|
2208
|
+
declaredTechnologies.length > 0;
|
|
2209
|
+
// P1-A (0.20.0-beta.22): a decisão de needs_input é UMA e vive no motor — indexada
|
|
2210
|
+
// à activação produzida, não à presença de campos. O prepare reage ao veredicto
|
|
2211
|
+
// (abaixo, depois de correr a selecção), em vez de ter a sua própria regra.
|
|
2212
|
+
const activation = activate(input, { declaredOnly: declarativeSelection });
|
|
2157
2213
|
// (c) The scope gate measures the request's FOCUS, not its full semantic
|
|
2158
2214
|
// expansion: explicit concerns when given, else the concerns activated by
|
|
2159
2215
|
// deterministic sources (explicit input + direct lexicon task terms). Semantic
|
|
@@ -2173,7 +2229,22 @@ function prepareCodegenContextCore(raw) {
|
|
|
2173
2229
|
void focusConcerns; // kept for the debug notes below; the gate no longer counts requirements
|
|
2174
2230
|
// MP1 selection (G-mp1a O2): the engine composes baseline ∪ context and narrows
|
|
2175
2231
|
// by the task's declared signals — this is the requirement set served.
|
|
2176
|
-
const selection = runSelectionWithActivation(input, activation);
|
|
2232
|
+
const selection = runSelectionWithActivation(input, activation, selectionMode === "discover" ? (raw.technologies ?? []) : declaredTechnologies, selectionMode);
|
|
2233
|
+
if (selection.needs_input) {
|
|
2234
|
+
const ni = selection.needs_input;
|
|
2235
|
+
return blocked(input, raw, "needs_input", [
|
|
2236
|
+
ni.reason,
|
|
2237
|
+
"Contrato v1.18-beta: o servidor responde ao declarado e NÃO interpreta o `task` — que fica registado para auditoria."
|
|
2238
|
+
], [
|
|
2239
|
+
`Lê o vocabulário fechado: read_sbd_toe_resource(uri="${ni.vocabulary_resource}").`,
|
|
2240
|
+
`Re-chama declarando, por exemplo: ${ni.example.with}.`,
|
|
2241
|
+
...(ni.candidates_to_confirm.from_task_text.length > 0
|
|
2242
|
+
? [`SUGESTÃO A CONFIRMAR (não é selecção), derivada do texto: [${ni.candidates_to_confirm.from_task_text.join(", ")}] — confirma e declara.`]
|
|
2243
|
+
: []),
|
|
2244
|
+
...(ni.inert_declarations?.length ? [`Declarações inertes nesta chamada: ${ni.inert_declarations.join("; ")}.`] : []),
|
|
2245
|
+
`Queres o comportamento inferencial antigo? selection_mode="discover" (exploratório).`
|
|
2246
|
+
], activation.trace);
|
|
2247
|
+
}
|
|
2177
2248
|
const estimatedRequirements = selection.selected.length;
|
|
2178
2249
|
// 0.19.4 («a promessa do minimal», lead opção 2): tecto de requisitos por-id
|
|
2179
2250
|
// por nível de detail, derivado da medição (~68 tk/req min/std, ~29 ultrathin)
|
|
@@ -2232,7 +2303,11 @@ function prepareCodegenContextCore(raw) {
|
|
|
2232
2303
|
const postGate = gateAfterActivation({
|
|
2233
2304
|
input,
|
|
2234
2305
|
activation,
|
|
2235
|
-
estimatedRequirements
|
|
2306
|
+
estimatedRequirements,
|
|
2307
|
+
// Só é "declarado" quando o chamador declarou mesmo: no caminho declarativo a
|
|
2308
|
+
// ausência de declarações já devolveu needs_input antes de chegar aqui, logo
|
|
2309
|
+
// este ponto implica declaração real. Em discover o gate mantém-se inteiro.
|
|
2310
|
+
declaredSurfaces: declarativeSelection && hasDeclaredActivator
|
|
2236
2311
|
});
|
|
2237
2312
|
if (postGate && postGate.status !== "ready_for_codegen") {
|
|
2238
2313
|
return blocked(input, raw, postGate.status, postGate.reasons, postGate.suggestions, activation.trace, { rejected: activation.rejected, notes: activation.notes });
|