muonroi-cli 1.4.1 → 1.6.0
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/LICENSE +21 -21
- package/README.md +122 -122
- package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
- package/dist/src/agent-harness/__tests__/mock-model.spec.js +48 -1
- package/dist/src/agent-harness/mock-model.d.ts +11 -0
- package/dist/src/agent-harness/mock-model.js +21 -0
- package/dist/src/cli/cost-forensics.js +12 -12
- package/dist/src/council/__tests__/clarification-prompt.test.js +51 -0
- package/dist/src/council/__tests__/clarifier-ready-gate.test.js +32 -0
- package/dist/src/council/__tests__/decisions-lock.test.js +17 -1
- package/dist/src/council/__tests__/oauth-reachable.test.d.ts +1 -0
- package/dist/src/council/__tests__/oauth-reachable.test.js +31 -0
- package/dist/src/council/__tests__/parse-outcome-fallback.test.js +11 -0
- package/dist/src/council/clarifier.js +9 -1
- package/dist/src/council/debate.js +5 -1
- package/dist/src/council/decisions-lock.js +3 -3
- package/dist/src/council/index.js +12 -5
- package/dist/src/council/leader.d.ts +0 -17
- package/dist/src/council/leader.js +22 -15
- package/dist/src/council/planner.js +1 -1
- package/dist/src/council/prompts.js +63 -57
- package/dist/src/council/types.d.ts +7 -0
- package/dist/src/ee/__tests__/ee-onboarding.test.d.ts +1 -0
- package/dist/src/ee/__tests__/ee-onboarding.test.js +32 -0
- package/dist/src/ee/artifact-cache.d.ts +56 -0
- package/dist/src/ee/artifact-cache.js +155 -0
- package/dist/src/ee/artifact-cache.test.d.ts +1 -0
- package/dist/src/ee/artifact-cache.test.js +69 -0
- package/dist/src/ee/auth.d.ts +9 -0
- package/dist/src/ee/auth.js +19 -0
- package/dist/src/ee/ee-onboarding.d.ts +5 -0
- package/dist/src/ee/ee-onboarding.js +76 -0
- package/dist/src/ee/search.js +7 -5
- package/dist/src/ee/search.test.d.ts +1 -0
- package/dist/src/ee/search.test.js +23 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/headless/output.js +6 -4
- package/dist/src/headless/output.test.js +4 -3
- package/dist/src/index.js +20 -1
- package/dist/src/mcp/__tests__/auto-setup.test.js +74 -0
- package/dist/src/mcp/__tests__/client-pool.spec.d.ts +1 -0
- package/dist/src/mcp/__tests__/client-pool.spec.js +98 -0
- package/dist/src/mcp/__tests__/parallel-build.spec.d.ts +1 -0
- package/dist/src/mcp/__tests__/parallel-build.spec.js +67 -0
- package/dist/src/mcp/__tests__/smart-filter.test.js +56 -0
- package/dist/src/mcp/auto-setup.js +56 -2
- package/dist/src/mcp/client-pool.d.ts +46 -0
- package/dist/src/mcp/client-pool.js +212 -0
- package/dist/src/mcp/oauth-callback.js +2 -2
- package/dist/src/mcp/parse-headers.test.js +14 -14
- package/dist/src/mcp/runtime.d.ts +28 -0
- package/dist/src/mcp/runtime.js +117 -51
- package/dist/src/mcp/self-verify-runner.d.ts +14 -0
- package/dist/src/mcp/self-verify-runner.js +38 -0
- package/dist/src/mcp/setup-guide-text.d.ts +9 -0
- package/dist/src/mcp/setup-guide-text.js +84 -0
- package/dist/src/mcp/smart-filter.js +49 -0
- package/dist/src/mcp/smoke.test.js +43 -43
- package/dist/src/mcp/tools-server.d.ts +7 -0
- package/dist/src/mcp/tools-server.js +19 -22
- package/dist/src/models/catalog.json +349 -349
- package/dist/src/ops/__tests__/doctor-ee-health.test.js +21 -0
- package/dist/src/ops/doctor.d.ts +3 -2
- package/dist/src/ops/doctor.js +47 -11
- package/dist/src/ops/doctor.test.js +4 -3
- package/dist/src/orchestrator/__tests__/mcp-capability-block.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/mcp-capability-block.test.js +39 -0
- package/dist/src/orchestrator/__tests__/project-stack.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/project-stack.test.js +65 -0
- package/dist/src/orchestrator/batch-turn-runner.js +7 -11
- package/dist/src/orchestrator/compaction.d.ts +2 -0
- package/dist/src/orchestrator/compaction.js +14 -1
- package/dist/src/orchestrator/compaction.test.js +25 -1
- package/dist/src/orchestrator/message-processor.js +72 -32
- package/dist/src/orchestrator/orchestrator.js +26 -0
- package/dist/src/orchestrator/prompts.d.ts +51 -0
- package/dist/src/orchestrator/prompts.js +257 -134
- package/dist/src/orchestrator/scope-ceiling.js +6 -1
- package/dist/src/orchestrator/scope-reminder.d.ts +12 -0
- package/dist/src/orchestrator/scope-reminder.js +16 -0
- package/dist/src/orchestrator/scope-reminder.test.js +22 -1
- package/dist/src/orchestrator/stream-runner.js +23 -15
- package/dist/src/orchestrator/subagent-compactor.d.ts +14 -5
- package/dist/src/orchestrator/subagent-compactor.js +30 -8
- package/dist/src/orchestrator/subagent-compactor.spec.js +18 -0
- package/dist/src/orchestrator/text-tool-call-detector.test.js +13 -13
- package/dist/src/pil/__tests__/clarity-gate.test.js +24 -215
- package/dist/src/pil/__tests__/config.test.js +1 -17
- package/dist/src/pil/__tests__/discovery.test.js +144 -11
- package/dist/src/pil/__tests__/layer1-intent-trace.test.js +7 -2
- package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
- package/dist/src/pil/__tests__/layer16-clarity.test.js +32 -116
- package/dist/src/pil/__tests__/layer4-gsd.test.js +37 -0
- package/dist/src/pil/__tests__/layer6-output.test.js +158 -18
- package/dist/src/pil/__tests__/llm-classify.test.js +49 -2
- package/dist/src/pil/__tests__/surface-compaction-artifacts.test.d.ts +1 -0
- package/dist/src/pil/__tests__/surface-compaction-artifacts.test.js +112 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +2 -0
- package/dist/src/pil/agent-operating-contract.test.js +7 -2
- package/dist/src/pil/cheap-model-playbook.js +35 -35
- package/dist/src/pil/cheap-model-workbooks.js +16 -13
- package/dist/src/pil/clarity-gate.d.ts +21 -19
- package/dist/src/pil/clarity-gate.js +26 -153
- package/dist/src/pil/config.d.ts +9 -1
- package/dist/src/pil/config.js +15 -4
- package/dist/src/pil/discovery.js +211 -136
- package/dist/src/pil/layer1-intent.d.ts +12 -0
- package/dist/src/pil/layer1-intent.js +283 -38
- package/dist/src/pil/layer1-intent.test.js +210 -4
- package/dist/src/pil/layer16-clarity.d.ts +25 -11
- package/dist/src/pil/layer16-clarity.js +19 -306
- package/dist/src/pil/layer3-ee-injection.d.ts +19 -0
- package/dist/src/pil/layer3-ee-injection.js +96 -4
- package/dist/src/pil/layer4-gsd.js +18 -6
- package/dist/src/pil/layer6-output.d.ts +2 -0
- package/dist/src/pil/layer6-output.js +151 -25
- package/dist/src/pil/llm-classify.d.ts +26 -0
- package/dist/src/pil/llm-classify.js +34 -5
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +82 -76
- package/dist/src/pil/pipeline.js +15 -9
- package/dist/src/pil/schema.d.ts +8 -0
- package/dist/src/pil/schema.js +12 -1
- package/dist/src/pil/task-tier-map.js +4 -0
- package/dist/src/pil/types.d.ts +11 -1
- package/dist/src/product-loop/done-gate.js +3 -3
- package/dist/src/product-loop/loop-driver.js +18 -18
- package/dist/src/product-loop/progress-snapshot.js +4 -4
- package/dist/src/providers/auth/gemini-oauth.js +6 -15
- package/dist/src/providers/auth/grok-oauth.js +6 -15
- package/dist/src/providers/auth/openai-oauth.js +6 -15
- package/dist/src/providers/mcp-vision-bridge.js +48 -48
- package/dist/src/reporter/index.js +1 -1
- package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
- package/dist/src/scaffold/bb-quality-gate.js +5 -5
- package/dist/src/scaffold/continuation-prompt.js +60 -60
- package/dist/src/scaffold/init-new.js +453 -453
- package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
- package/dist/src/self-qa/agentic-loop.js +24 -19
- package/dist/src/self-qa/spec-emitter.js +26 -23
- package/dist/src/storage/__tests__/migrations.test.js +2 -2
- package/dist/src/storage/interaction-log.js +5 -5
- package/dist/src/storage/migrations.js +122 -122
- package/dist/src/storage/sessions.js +42 -42
- package/dist/src/storage/transcript.js +91 -84
- package/dist/src/storage/usage.js +14 -14
- package/dist/src/storage/workspaces.js +12 -12
- package/dist/src/tools/__tests__/native-tools.test.d.ts +1 -0
- package/dist/src/tools/__tests__/native-tools.test.js +53 -0
- package/dist/src/tools/git-safety.d.ts +61 -0
- package/dist/src/tools/git-safety.js +141 -0
- package/dist/src/tools/git-safety.test.d.ts +1 -0
- package/dist/src/tools/git-safety.test.js +111 -0
- package/dist/src/tools/native-tools.d.ts +31 -0
- package/dist/src/tools/native-tools.js +273 -0
- package/dist/src/tools/registry-ee-query.test.js +18 -1
- package/dist/src/tools/registry-git-safety.test.d.ts +7 -0
- package/dist/src/tools/registry-git-safety.test.js +92 -0
- package/dist/src/tools/registry.js +52 -6
- package/dist/src/ui/__tests__/markdown-render.test.d.ts +1 -0
- package/dist/src/ui/__tests__/markdown-render.test.js +48 -0
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/components/message-view.js +4 -1
- package/dist/src/ui/components/structured-response-view.js +7 -3
- package/dist/src/ui/components/tool-group.js +7 -1
- package/dist/src/ui/markdown-render.d.ts +41 -0
- package/dist/src/ui/markdown-render.js +223 -0
- package/dist/src/ui/markdown.d.ts +10 -0
- package/dist/src/ui/markdown.js +12 -35
- package/dist/src/ui/slash/council-inspect.js +4 -4
- package/dist/src/ui/slash/export.js +4 -4
- package/dist/src/ui/utils/text.d.ts +8 -0
- package/dist/src/ui/utils/text.js +16 -0
- package/dist/src/ui/utils/text.test.d.ts +1 -0
- package/dist/src/ui/utils/text.test.js +23 -0
- package/dist/src/usage/ledger.js +48 -15
- package/dist/src/utils/__tests__/footprint-gitignore.test.d.ts +1 -0
- package/dist/src/utils/__tests__/footprint-gitignore.test.js +50 -0
- package/dist/src/utils/clipboard-image.js +23 -23
- package/dist/src/utils/open-url.d.ts +56 -0
- package/dist/src/utils/open-url.js +58 -0
- package/dist/src/utils/open-url.test.d.ts +1 -0
- package/dist/src/utils/open-url.test.js +86 -0
- package/dist/src/utils/settings.d.ts +12 -0
- package/dist/src/utils/settings.js +48 -0
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/skills.js +3 -3
- package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
- package/dist/src/verify/environment.js +2 -1
- package/package.json +1 -1
- package/dist/src/pil/layer16-clarity.test.js +0 -31
- /package/dist/src/{pil/layer16-clarity.test.d.ts → council/__tests__/clarification-prompt.test.d.ts} +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { detectNoClarifySignal } from "./clarity-gate.js";
|
|
3
3
|
import { getMaxInterviewQuestions, isDiscoveryEnabled } from "./config.js";
|
|
4
4
|
import { getCachedProjectContext, setCachedProjectContext } from "./discovery-cache.js";
|
|
5
5
|
import { isMetaAnalysisPrompt } from "./layer6-output.js";
|
|
6
6
|
import { scanProjectContext } from "./layer15-context-scan.js";
|
|
7
|
-
import { buildInterviewQuestion,
|
|
7
|
+
import { buildInterviewQuestion, getAutofilledOutcome, isProvideOwnDetailsSentinel, PROVIDE_OWN_DETAILS_OPTION_EN, resolveGapsNonInteractive, } from "./layer16-clarity.js";
|
|
8
8
|
import { checkFeasibility } from "./layer17-feasibility.js";
|
|
9
9
|
import { buildAcceptanceCard, buildAcceptanceQuestion } from "./layer18-acceptance.js";
|
|
10
|
-
import {
|
|
10
|
+
import { markDiscoveryAccepted } from "./session-state.js";
|
|
11
11
|
function emptyProjectContext(cwd) {
|
|
12
12
|
return {
|
|
13
13
|
language: null,
|
|
@@ -44,21 +44,36 @@ export async function runDiscovery(raw, l1, cwd, handler, sessionId = null, clar
|
|
|
44
44
|
return baseResult();
|
|
45
45
|
if (l1.intentKind === "chitchat" || l1.taskType === null)
|
|
46
46
|
return baseResult();
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
// fix it?" forces the user to re-type their intent as a freetext answer,
|
|
52
|
-
// which PIL then mis-routes through gap-resolution + acceptance, producing
|
|
53
|
-
// duplicate askcards (evidence: session 1f29e238a816 timeline).
|
|
54
|
-
const sessionState = getSessionState(sessionId);
|
|
55
|
-
if (sessionState && sessionState.turnCount > 1 && isLikelyFollowUp(raw)) {
|
|
47
|
+
// The user explicitly told the agent not to clarify ("don't ask" / "trả lời
|
|
48
|
+
// thẳng"). This is the USER's consent decision, not a classification heuristic,
|
|
49
|
+
// so it stays: skip the entire interview + acceptance ceremony.
|
|
50
|
+
if (detectNoClarifySignal(raw))
|
|
56
51
|
return baseResult();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
// ── Model-driven clarification gate (Phase 2) ──────────────────────────────
|
|
53
|
+
// The configured chat model — NOT a regex/keyword heuristic — is the sole
|
|
54
|
+
// decider of whether this turn has a genuine gray area, what to ask, which
|
|
55
|
+
// options to offer, why, and which option is recommended (user directive
|
|
56
|
+
// 2026-06-16: "các askcard bắt buộc xuất phát từ model muốn hỏi"). The CLI only
|
|
57
|
+
// injects the proposer prompt to open the path.
|
|
58
|
+
//
|
|
59
|
+
// There is deliberately NO regex fallback. The old path ran shouldAutoPass() +
|
|
60
|
+
// detectClarityGaps() keyword heuristics and fabricated questions/outcomes from
|
|
61
|
+
// them — the exact "phân loại task qua regex từ khoá ... bad bad bad UX, miss
|
|
62
|
+
// hàng tỷ case" behaviour this phase removes (it also fabricated a build outcome
|
|
63
|
+
// for a yes/no question — live repro f6f7881a5fae). If the model cannot propose
|
|
64
|
+
// questions (no proposer wired, or it throws), we log loudly and proceed WITHOUT
|
|
65
|
+
// an interview; we never invent a question from keywords ("không bao giờ hardcode
|
|
66
|
+
// fallback... có vấn đề = fail = ghi logs"). The agent can still clarify inline.
|
|
67
|
+
if (!clarificationProposer) {
|
|
68
|
+
// Interactive turns always wire a proposer (orchestrator/message-processor).
|
|
69
|
+
// A missing one there is a wiring bug — surface it, never paper over with regex.
|
|
70
|
+
if (handler) {
|
|
71
|
+
console.error("[Agent:discovery] interactive turn has no model clarification proposer — skipping interview (no regex fallback by design)");
|
|
72
|
+
}
|
|
60
73
|
return baseResult();
|
|
61
|
-
|
|
74
|
+
}
|
|
75
|
+
// L1.5: Context Discovery (cacheable) — gives the model real project facts so
|
|
76
|
+
// it never asks for something it can inspect itself (language/framework/modules).
|
|
62
77
|
let projectContext;
|
|
63
78
|
const cached = getCachedProjectContext(cwd);
|
|
64
79
|
if (cached) {
|
|
@@ -76,78 +91,23 @@ export async function runDiscovery(raw, l1, cwd, handler, sessionId = null, clar
|
|
|
76
91
|
projectContext = emptyProjectContext(cwd);
|
|
77
92
|
}
|
|
78
93
|
}
|
|
79
|
-
// L1.6:
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// Handler only decides whether to show interactive askcard.
|
|
87
|
-
if (clarificationProposer) {
|
|
88
|
-
try {
|
|
89
|
-
const additionalContext = [
|
|
90
|
-
projectContext.language ? `Language: ${projectContext.language}` : "",
|
|
91
|
-
projectContext.framework ? `Framework: ${projectContext.framework}` : "",
|
|
92
|
-
projectContext.packageManager ? `Package manager: ${projectContext.packageManager}` : "",
|
|
93
|
-
projectContext.relevantModules?.length
|
|
94
|
-
? `Relevant modules: ${projectContext.relevantModules.map((m) => m.path).join(", ")}`
|
|
95
|
-
: "",
|
|
96
|
-
projectContext.boundedContexts?.length
|
|
97
|
-
? `Bounded contexts: ${projectContext.boundedContexts.map((b) => `${b.name} (${b.path})`).join(", ")}`
|
|
98
|
-
: "",
|
|
99
|
-
projectContext.eePatterns?.length ? `EE patterns: ${projectContext.eePatterns.slice(0, 3).join(" | ")}` : "",
|
|
100
|
-
recentTurnsSummary ? `\nRecent Conversation History:\n${recentTurnsSummary}` : "",
|
|
101
|
-
]
|
|
102
|
-
.filter(Boolean)
|
|
103
|
-
.join("\n");
|
|
104
|
-
const modelQuestions = await clarificationProposer({
|
|
105
|
-
raw,
|
|
106
|
-
l1: { taskType: l1.taskType, confidence: l1.confidence },
|
|
107
|
-
additionalContext: additionalContext || undefined,
|
|
108
|
-
});
|
|
109
|
-
if (modelQuestions.length > 0) {
|
|
110
|
-
gaps = modelQuestions.slice(0, 3).map((line, idx) => {
|
|
111
|
-
let q = line;
|
|
112
|
-
let recs = ["I will provide my own details / constraints"];
|
|
113
|
-
const m = line.match(/\[MODEL RECS:?\s*(.+?)\]/i) || line.match(/RECS:\s*(.+)$/i);
|
|
114
|
-
if (m) {
|
|
115
|
-
recs = m[1]
|
|
116
|
-
.split(/\s*\|\s*/)
|
|
117
|
-
.map((r) => r.trim())
|
|
118
|
-
.filter(Boolean)
|
|
119
|
-
.slice(0, 3);
|
|
120
|
-
q = line
|
|
121
|
-
.replace(/\[MODEL RECS:?.*?\]/i, "")
|
|
122
|
-
.replace(/RECS:.*$/, "")
|
|
123
|
-
.trim();
|
|
124
|
-
}
|
|
125
|
-
return {
|
|
126
|
-
dimension: "outcome",
|
|
127
|
-
description: `Model-generated clarification #${idx + 1}`,
|
|
128
|
-
suggestedQuestion: q || "What else needs clarification?",
|
|
129
|
-
options: [...recs, "Other (type free answer)"],
|
|
130
|
-
defaultIndex: 0,
|
|
131
|
-
};
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
// fall through to static
|
|
137
|
-
}
|
|
94
|
+
// L1.6: Ask the MODEL what (if anything) it still needs. The model owns the
|
|
95
|
+
// gray-area decision, the questions, the options, the "why", and the
|
|
96
|
+
// recommended default. An empty result means it sees nothing worth asking →
|
|
97
|
+
// no interview, no fabricated [Discovery] outcome.
|
|
98
|
+
let gaps;
|
|
99
|
+
try {
|
|
100
|
+
gaps = await proposeModelGaps(clarificationProposer, raw, l1, projectContext, recentTurnsSummary);
|
|
138
101
|
}
|
|
139
|
-
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
description: "Specific outcome and constraints the agent/model needs from the user",
|
|
145
|
-
suggestedQuestion: `Để tôi (agent/model) thực hiện chính xác và có được thông tin cần thiết cho task này, bạn hãy cho tôi biết: kết quả mong muốn cụ thể, các ràng buộc quan trọng, hoặc bất kỳ chi tiết nào khác mà tôi cần làm rõ trước khi bắt đầu?`,
|
|
146
|
-
options: ["Tôi sẽ trả lời tự do / cung cấp chi tiết cần thiết"],
|
|
147
|
-
defaultIndex: 0,
|
|
148
|
-
},
|
|
149
|
-
];
|
|
102
|
+
catch (err) {
|
|
103
|
+
// No Silent Catch + fail-loud: log with context, then proceed WITHOUT an
|
|
104
|
+
// interview. We do NOT fall back to regex-generated questions.
|
|
105
|
+
console.error(`[Agent:discovery] model clarification proposer threw — proceeding without interview (no regex fallback): ${err?.message}`, { stack: err?.stack?.split("\n").slice(0, 3) });
|
|
106
|
+
return baseResult();
|
|
150
107
|
}
|
|
108
|
+
// Model decided there is no gray area worth asking about → proceed directly.
|
|
109
|
+
if (gaps.length === 0)
|
|
110
|
+
return baseResult();
|
|
151
111
|
let clarifiedIntent;
|
|
152
112
|
let interviewed = false;
|
|
153
113
|
if (gaps.length > 0 && handler) {
|
|
@@ -168,15 +128,15 @@ export async function runDiscovery(raw, l1, cwd, handler, sessionId = null, clar
|
|
|
168
128
|
else {
|
|
169
129
|
clarifiedIntent = resolveGapsNonInteractive(gaps, projectContext, raw);
|
|
170
130
|
}
|
|
171
|
-
// Auto-fill outcome for analyze/plan/documentation when no outcome gap was asked
|
|
172
|
-
//
|
|
173
|
-
//
|
|
131
|
+
// Auto-fill outcome for analyze/plan/documentation when no outcome gap was asked.
|
|
132
|
+
// Override only when the resolved outcome is a raw-derived generic ("Complete: …" /
|
|
133
|
+
// "Complete the task …") or a genuine filesystem-path leak (Local path, project root,
|
|
134
|
+
// "src/foo.ts" scope-option shapes). It must NOT clobber a legitimate user answer that
|
|
135
|
+
// merely contains a slash ("support REST/GraphQL", "input/output") — see looksLikePathLeak.
|
|
174
136
|
const autoOutcome = getAutofilledOutcome(l1.taskType, raw);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
/Local path|In prompts|directory as|project root|\/|absolute|local\/repo/i.test(clarifiedIntent.outcome) ||
|
|
179
|
-
clarifiedIntent.outcome.includes("/"))) {
|
|
137
|
+
const currentOutcome = clarifiedIntent.outcome ?? "";
|
|
138
|
+
const isGenericComplete = /^Complete(?::| the task)/i.test(currentOutcome);
|
|
139
|
+
if (autoOutcome && (!currentOutcome || isGenericComplete || looksLikePathLeak(currentOutcome))) {
|
|
180
140
|
clarifiedIntent = { ...clarifiedIntent, outcome: autoOutcome };
|
|
181
141
|
}
|
|
182
142
|
// L1.7: Feasibility Check
|
|
@@ -197,33 +157,32 @@ export async function runDiscovery(raw, l1, cwd, handler, sessionId = null, clar
|
|
|
197
157
|
accepted = false;
|
|
198
158
|
}
|
|
199
159
|
else if (decision === "adjust") {
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
// a
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
160
|
+
// The user asked to change something — let the MODEL re-propose questions
|
|
161
|
+
// given the same context (it owns the gray-area decision, exactly like the
|
|
162
|
+
// initial pass). On a proposer failure we keep the already-resolved intent
|
|
163
|
+
// rather than fabricate regex questions.
|
|
164
|
+
let reGaps = [];
|
|
165
|
+
try {
|
|
166
|
+
reGaps = await proposeModelGaps(clarificationProposer, raw, l1, projectContext, recentTurnsSummary);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
console.error(`[Agent:discovery] re-interview proposer threw — keeping prior intent: ${err?.message}`);
|
|
170
|
+
}
|
|
171
|
+
if (reGaps.length > 0) {
|
|
172
|
+
const reAnswered = reGaps.map((g) => ({
|
|
173
|
+
...g,
|
|
174
|
+
answer: null,
|
|
175
|
+
}));
|
|
176
|
+
const maxRe = Math.min(reGaps.length, getMaxInterviewQuestions());
|
|
177
|
+
for (let i = 0; i < maxRe; i++) {
|
|
178
|
+
const gap = reAnswered[i];
|
|
179
|
+
const q = buildInterviewQuestion(gap, randomUUID());
|
|
180
|
+
const ans = await handler.askQuestion(q);
|
|
181
|
+
reAnswered[i] = { ...gap, answer: ans.text };
|
|
220
182
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
reAnswered[i] = { ...gap, answer: ans.text };
|
|
183
|
+
clarifiedIntent = buildClarifiedIntentFromAnswers(reAnswered, raw, projectContext);
|
|
184
|
+
feasibility = await checkFeasibility(clarifiedIntent, projectContext).catch(() => feasibility);
|
|
224
185
|
}
|
|
225
|
-
clarifiedIntent = buildClarifiedIntentFromAnswers(reAnswered, raw, projectContext);
|
|
226
|
-
feasibility = await checkFeasibility(clarifiedIntent, projectContext).catch(() => feasibility);
|
|
227
186
|
accepted = true;
|
|
228
187
|
}
|
|
229
188
|
}
|
|
@@ -250,11 +209,39 @@ export async function runDiscovery(raw, l1, cwd, handler, sessionId = null, clar
|
|
|
250
209
|
discoveryMs: Date.now() - start,
|
|
251
210
|
};
|
|
252
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* True only for outcomes that are genuine filesystem-path leakage — known
|
|
214
|
+
* leaked-scope phrases ("Local path", "project root", …) or a real path segment
|
|
215
|
+
* ("src/foo.ts", a "src/cli (cli)" scope-option shape) that also carries a
|
|
216
|
+
* path-ish signal (file extension, path keyword, or trailing "(name)").
|
|
217
|
+
*
|
|
218
|
+
* A bare "/" is NOT sufficient: "support REST/GraphQL", "validate input/output",
|
|
219
|
+
* and "details / constraints" use the slash as an "or" separator and must be
|
|
220
|
+
* preserved. The previous implementation matched any "/" and silently clobbered
|
|
221
|
+
* such legitimate answers with the generic taskType default.
|
|
222
|
+
*/
|
|
223
|
+
function looksLikePathLeak(outcome) {
|
|
224
|
+
// Anchored known leaked-scope phrases (preserves prior override behaviour).
|
|
225
|
+
if (/(?:\b(?:local path|in prompts|directory as|project root|absolute)\b)|local\/repo/i.test(outcome)) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
// word/word path segment (no spaces around the slash) — e.g. "src/foo.ts".
|
|
229
|
+
if (!/\b[\w.-]+\/[\w./-]+/.test(outcome))
|
|
230
|
+
return false;
|
|
231
|
+
const hasFileExtension = /\/[\w-]+\.[a-z0-9]{1,5}\b/i.test(outcome); // ".../foo.ts"
|
|
232
|
+
const hasPathKeyword = /\b(?:path|dir|directory|folder|repo|root|module|src|lib|dist|tests?)\b/i.test(outcome);
|
|
233
|
+
const hasScopeOptionSuffix = /\([^)]+\)\s*$/.test(outcome); // "src/cli (cli)" scope-option shape
|
|
234
|
+
return hasFileExtension || hasPathKeyword || hasScopeOptionSuffix;
|
|
235
|
+
}
|
|
253
236
|
function buildClarifiedIntentFromAnswers(answeredGaps, raw, projectContext) {
|
|
254
237
|
const outcomeGap = answeredGaps.find((g) => g.dimension === "outcome");
|
|
255
238
|
const scopeGap = answeredGaps.find((g) => g.dimension === "scope");
|
|
256
239
|
const constraintGap = answeredGaps.find((g) => g.dimension === "constraint");
|
|
257
|
-
|
|
240
|
+
// The "provide my own details" meta-option is a no-answer sentinel; treat it
|
|
241
|
+
// as missing so the raw-derived generic (and downstream inferred outcome) is
|
|
242
|
+
// used instead of the sentinel string surviving verbatim as the outcome.
|
|
243
|
+
const outcomeAnswer = isProvideOwnDetailsSentinel(outcomeGap?.answer) ? null : (outcomeGap?.answer ?? null);
|
|
244
|
+
const outcome = outcomeAnswer ?? `Complete: ${raw.slice(0, 80)}`;
|
|
258
245
|
const scope = (() => {
|
|
259
246
|
if (scopeGap?.answer)
|
|
260
247
|
return [scopeGap.answer.replace(/\s*\(.*\)\s*$/, "").trim()];
|
|
@@ -279,6 +266,72 @@ function buildClarifiedIntentFromAnswers(answeredGaps, raw, projectContext) {
|
|
|
279
266
|
})),
|
|
280
267
|
};
|
|
281
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Build the enrichment context the model sees, call the proposer, and map each
|
|
271
|
+
* returned line into a ClarityGap. This is the SINGLE place discovery turns a
|
|
272
|
+
* model response into askcard gaps — the model owns the question, the options
|
|
273
|
+
* (recommends), the recommended default (first option), and the "why" (threaded
|
|
274
|
+
* into the gap description → askcard context). No regex/keyword gap synthesis.
|
|
275
|
+
*
|
|
276
|
+
* Throws on proposer error so the caller can decide how to degrade (initial pass
|
|
277
|
+
* proceeds without interview; the "adjust" pass keeps the prior intent).
|
|
278
|
+
*/
|
|
279
|
+
async function proposeModelGaps(proposer, raw, l1, projectContext, recentTurnsSummary) {
|
|
280
|
+
const additionalContext = [
|
|
281
|
+
projectContext.language ? `Language: ${projectContext.language}` : "",
|
|
282
|
+
projectContext.framework ? `Framework: ${projectContext.framework}` : "",
|
|
283
|
+
projectContext.packageManager ? `Package manager: ${projectContext.packageManager}` : "",
|
|
284
|
+
projectContext.relevantModules?.length
|
|
285
|
+
? `Relevant modules: ${projectContext.relevantModules.map((m) => m.path).join(", ")}`
|
|
286
|
+
: "",
|
|
287
|
+
projectContext.boundedContexts?.length
|
|
288
|
+
? `Bounded contexts: ${projectContext.boundedContexts.map((b) => `${b.name} (${b.path})`).join(", ")}`
|
|
289
|
+
: "",
|
|
290
|
+
projectContext.eePatterns?.length ? `EE patterns: ${projectContext.eePatterns.slice(0, 3).join(" | ")}` : "",
|
|
291
|
+
recentTurnsSummary ? `\nRecent Conversation History:\n${recentTurnsSummary}` : "",
|
|
292
|
+
]
|
|
293
|
+
.filter(Boolean)
|
|
294
|
+
.join("\n");
|
|
295
|
+
const modelQuestions = await proposer({
|
|
296
|
+
raw,
|
|
297
|
+
l1: { taskType: l1.taskType, confidence: l1.confidence },
|
|
298
|
+
additionalContext: additionalContext || undefined,
|
|
299
|
+
});
|
|
300
|
+
return modelQuestions.slice(0, 3).map(parseModelQuestionToGap);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Parse one proposer line ("question [MODEL RECS: a | b] [WHY: reason]") into a
|
|
304
|
+
* ClarityGap. The recommends become the askcard options (first = recommended
|
|
305
|
+
* default); the WHY clause becomes the askcard context so the user sees the
|
|
306
|
+
* model's own reason for asking.
|
|
307
|
+
*/
|
|
308
|
+
function parseModelQuestionToGap(line, idx) {
|
|
309
|
+
let recs = [PROVIDE_OWN_DETAILS_OPTION_EN];
|
|
310
|
+
const recMatch = line.match(/\[MODEL RECS:?\s*(.+?)\]/i) || line.match(/RECS:\s*(.+)$/i);
|
|
311
|
+
if (recMatch) {
|
|
312
|
+
const parsed = recMatch[1]
|
|
313
|
+
.split(/\s*\|\s*/)
|
|
314
|
+
.map((r) => r.trim())
|
|
315
|
+
.filter(Boolean)
|
|
316
|
+
.slice(0, 3);
|
|
317
|
+
if (parsed.length > 0)
|
|
318
|
+
recs = parsed;
|
|
319
|
+
}
|
|
320
|
+
const whyMatch = line.match(/\[WHY:\s*(.+?)\]/i);
|
|
321
|
+
const why = whyMatch ? whyMatch[1].trim() : "";
|
|
322
|
+
const question = line
|
|
323
|
+
.replace(/\[WHY:.*?\]/i, "")
|
|
324
|
+
.replace(/\[MODEL RECS:?.*?\]/i, "")
|
|
325
|
+
.replace(/RECS:.*$/, "")
|
|
326
|
+
.trim();
|
|
327
|
+
return {
|
|
328
|
+
dimension: "outcome",
|
|
329
|
+
description: why || `Model-generated clarification #${idx + 1}`,
|
|
330
|
+
suggestedQuestion: question || "What else needs clarification?",
|
|
331
|
+
options: [...recs, "Other (type free answer)"],
|
|
332
|
+
defaultIndex: 0,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
282
335
|
/**
|
|
283
336
|
* Create a ModelClarificationProposer backed by the actual task model.
|
|
284
337
|
* The model receives the user raw + CLI enrichment (l1, project modules, etc.)
|
|
@@ -297,43 +350,65 @@ export function createModelClarificationProposer(providerFactory, modelId) {
|
|
|
297
350
|
const special = isMetaAnalysisPrompt(input.raw)
|
|
298
351
|
? `\nIf the request is a self-evaluation, meta-analysis or review of the CLI by the agent running inside it, do NOT ask about repo path, current directory, absolute path, local repo location or "which directory". Scope is always the full project root. Focus questions and recommends on which CLI internals (PIL, discovery, tools, compaction, EE, model BE, loop guard) to evaluate or specific improvements to assess after fixes. Use the enrichment context.`
|
|
299
352
|
: "";
|
|
353
|
+
// Environment/self header — the main system prompt has buildEnvironmentBlock,
|
|
354
|
+
// but THIS discovery question-generator is a separate LLM call that lacked it,
|
|
355
|
+
// so it assumed Python and asked the user to paste the directory tree despite
|
|
356
|
+
// running inside the repo (live grok session). Escape hatch:
|
|
357
|
+
// MUONROI_DISCOVERY_SKIP_ENV_CONTEXT=1.
|
|
358
|
+
const osLabel = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
|
|
359
|
+
const envHeader = process.env.MUONROI_DISCOVERY_SKIP_ENV_CONTEXT === "1"
|
|
360
|
+
? ""
|
|
361
|
+
: `Runtime: ${osLabel} (${process.platform}); \`bash\` is POSIX. The project's language/framework is in the context below — do NOT assume Python or a POSIX-only layout. Do NOT ask the user to paste the directory tree, file list, or project structure: you run INSIDE the repository and can inspect it with your own tools. Ask only about genuine intent / scope ambiguities.\n`;
|
|
300
362
|
const prompt = `You are the AI agent executing inside muonroi-cli.
|
|
301
|
-
User request: "${input.raw}"
|
|
363
|
+
${envHeader}User request: "${input.raw}"
|
|
302
364
|
Task type from CLI: ${input.l1.taskType}
|
|
303
365
|
${contextStr}
|
|
304
366
|
|
|
305
|
-
|
|
306
|
-
If the User request is a follow-up or continuation of the recent conversation history (if provided above), do NOT ask for new project details; assume the context is already established and return
|
|
307
|
-
Consider the provided language/framework/modules/EE patterns when suggesting questions and recs —
|
|
308
|
-
For each question
|
|
367
|
+
You decide whether this turn has a genuine gray area that BLOCKS you from delivering correctly. Output the FEW specific, concise questions you (the model) genuinely still need answered — ask only what is truly blocking, not a quota. Most well-scoped requests need 0-1 questions. If everything you need is already inferable from the request + context above, OR the request is a plain question you can simply answer, return an empty array [].
|
|
368
|
+
If the User request is a follow-up or continuation of the recent conversation history (if provided above), do NOT ask for new project details; assume the context is already established and return [] unless there is a critical new ambiguity.
|
|
369
|
+
Consider the provided language/framework/modules/EE patterns when suggesting questions and recs — never ask something the context above already answers.${special}
|
|
370
|
+
For each question: (1) provide 1-2 short concrete recommendations the user can pick from, ALWAYS listing the ONE you would choose FIRST — it becomes the default the user accepts with one keypress; (2) give a short "reason" clause explaining WHY answering it changes what you do. Be decisive; do not hand back an unranked list.
|
|
309
371
|
Return ONLY valid JSON array, nothing else:
|
|
310
|
-
[{"question":"...","recommends":["rec1","rec2"]}, ...]
|
|
372
|
+
[{"question":"...","recommends":["rec1","rec2"],"reason":"why this matters"}, ...]
|
|
311
373
|
Max 3 items.`;
|
|
312
374
|
const result = await generateText({
|
|
313
375
|
model: runtime.model,
|
|
314
376
|
prompt,
|
|
315
|
-
maxOutputTokens:
|
|
377
|
+
maxOutputTokens: 320,
|
|
316
378
|
});
|
|
317
|
-
let items
|
|
379
|
+
let items;
|
|
318
380
|
try {
|
|
319
381
|
const txt = result.text
|
|
320
382
|
.trim()
|
|
321
383
|
.replace(/```json|```/g, "")
|
|
322
384
|
.trim();
|
|
323
|
-
|
|
385
|
+
const parsed = JSON.parse(txt);
|
|
386
|
+
items = Array.isArray(parsed) ? parsed : [];
|
|
324
387
|
}
|
|
325
|
-
catch {
|
|
326
|
-
//
|
|
327
|
-
|
|
388
|
+
catch (parseErr) {
|
|
389
|
+
// A malformed (non-JSON) model response must NOT be coerced into a junk
|
|
390
|
+
// askcard — log and return no questions (proceed without interview).
|
|
391
|
+
console.error(`[Agent:discovery] clarification proposer returned non-JSON — no questions this turn: ${parseErr?.message}`, { sample: result.text.slice(0, 160) });
|
|
392
|
+
return [];
|
|
328
393
|
}
|
|
329
|
-
return items
|
|
394
|
+
return items
|
|
395
|
+
.filter((it) => it && typeof it.question === "string" && it.question.trim())
|
|
396
|
+
.slice(0, 3)
|
|
397
|
+
.map((it) => {
|
|
330
398
|
const recs = (it.recommends || []).slice(0, 2).join(" | ");
|
|
331
|
-
const
|
|
332
|
-
|
|
399
|
+
const recTag = recs ? ` [MODEL RECS: ${recs}]` : "";
|
|
400
|
+
const why = (it.reason || "").trim();
|
|
401
|
+
const whyTag = why ? ` [WHY: ${why}]` : "";
|
|
402
|
+
return `${it.question.trim()}${recTag}${whyTag}`;
|
|
333
403
|
});
|
|
334
404
|
}
|
|
335
405
|
catch (err) {
|
|
336
|
-
// Silent
|
|
406
|
+
// No Silent Catch + fail-loud: the model call failed. Log with context and
|
|
407
|
+
// return no questions — discovery proceeds WITHOUT an interview rather than
|
|
408
|
+
// fabricating a regex-derived one ("có vấn đề = fail = ghi logs").
|
|
409
|
+
console.error(`[Agent:discovery] clarification proposer failed (${modelId}): ${err?.message}`, {
|
|
410
|
+
stack: err?.stack?.split("\n").slice(0, 3),
|
|
411
|
+
});
|
|
337
412
|
return [];
|
|
338
413
|
}
|
|
339
414
|
};
|
|
@@ -51,6 +51,18 @@ export declare function scoreComplexity(input: ComplexityInput): ComplexityOutpu
|
|
|
51
51
|
export declare function isTestGenerationTask(raw: string): boolean;
|
|
52
52
|
/** Detect optimization-verb prompts where refactor is the correct taskType. */
|
|
53
53
|
export declare function isPerformanceRefactor(raw: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Detect a greenfield CREATE/BUILD request whose correct taskType is `build`.
|
|
56
|
+
* Tight by construction: requires a LEADING creation verb + a software-artifact
|
|
57
|
+
* object, and vetoes build-failure/debug context. When unsure it returns false
|
|
58
|
+
* so the prompt cascades to the classifier + brain (no wrong deterministic pin).
|
|
59
|
+
*
|
|
60
|
+
* `build` is a first-class TaskType (greenfield project/feature creation) — it is
|
|
61
|
+
* the sole producer of that label. It mirrors `generate` for routing (tier/role/
|
|
62
|
+
* tokens/ceiling) but carries greenfield-specific outcome options + output rules.
|
|
63
|
+
* This replaces the F17 band-aid that pinned greenfield prompts to `generate`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function isGreenfieldBuildTask(raw: string): boolean;
|
|
54
66
|
/** Detect short continuation prompts ("tiếp tục", "ok", "continue", …). */
|
|
55
67
|
export declare function isContinuationPhrase(raw: string): boolean;
|
|
56
68
|
/**
|