muonroi-cli 1.4.1 → 1.5.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/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/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/message-processor.js +57 -27
- 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/stream-runner.js +20 -15
- 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 +137 -18
- package/dist/src/pil/__tests__/llm-classify.test.js +49 -2
- 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/layer4-gsd.js +18 -6
- package/dist/src/pil/layer6-output.d.ts +2 -0
- package/dist/src/pil/layer6-output.js +137 -22
- 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/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-git-safety.test.d.ts +7 -0
- package/dist/src/tools/registry-git-safety.test.js +92 -0
- package/dist/src/tools/registry.js +39 -4
- 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,19 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/pil/layer16-clarity.ts
|
|
3
|
+
*
|
|
4
|
+
* Phase 2 (2026-06-16): `detectClarityGaps` and its keyword option-builders
|
|
5
|
+
* (`buildOutcomeOptions` / `buildScopeOptions` / `pickBest*` / recency ranking)
|
|
6
|
+
* were removed. The configured chat model now decides every clarification —
|
|
7
|
+
* its questions, options, recommended default, and reason — in
|
|
8
|
+
* `proposeModelGaps` (`discovery.ts`). There is no regex gap synthesis.
|
|
9
|
+
*
|
|
10
|
+
* What remains here is gap RENDERING + RESOLUTION (consumed by the model path):
|
|
11
|
+
* - the "provide my own details" no-answer sentinel,
|
|
12
|
+
* - `buildInterviewQuestion` (ClarityGap → askcard),
|
|
13
|
+
* - `resolveGapsNonInteractive` (default-answer resolution when headless),
|
|
14
|
+
* - `getAutofilledOutcome` / `getDefaultOutcome` (outcome-label polish).
|
|
15
|
+
*/
|
|
1
16
|
import type { CouncilQuestionData } from "../types/index.js";
|
|
2
17
|
import type { ClarifiedIntent, ClarityGap, ProjectContext } from "./discovery-types.js";
|
|
3
18
|
import type { TaskType } from "./types.js";
|
|
4
|
-
export declare function detectClarityGaps(raw: string, taskType: TaskType | null, confidence: number, projectContext: ProjectContext): ClarityGap[];
|
|
5
19
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* was demoted to last. Only recommend a specific bounded context when the prompt
|
|
13
|
-
* literally names it (same word-overlap test buildScopeOptions uses); otherwise
|
|
14
|
-
* recommend "Entire project".
|
|
20
|
+
* The default "no specific answer" meta-option offered for a model-generated
|
|
21
|
+
* clarification when the model supplies no concrete recommendations. Selecting
|
|
22
|
+
* it means "use your judgment / I have nothing specific to add" — it is a
|
|
23
|
+
* sentinel, NOT a real outcome, so it must never surface verbatim as the
|
|
24
|
+
* resolved outcome. Centralised here so discovery.ts (which presents the
|
|
25
|
+
* option) and the outcome-resolution paths agree on the exact strings.
|
|
15
26
|
*/
|
|
16
|
-
export declare
|
|
27
|
+
export declare const PROVIDE_OWN_DETAILS_OPTION_EN = "I will provide my own details / constraints";
|
|
28
|
+
export declare const PROVIDE_OWN_DETAILS_OPTION_VI = "T\u00F4i s\u1EBD tr\u1EA3 l\u1EDDi t\u1EF1 do / cung c\u1EA5p chi ti\u1EBFt c\u1EA7n thi\u1EBFt";
|
|
29
|
+
/** True when an answer is the "I'll provide my own details" meta-option (any locale). */
|
|
30
|
+
export declare function isProvideOwnDetailsSentinel(answer: string | null | undefined): boolean;
|
|
17
31
|
export declare function buildInterviewQuestion(gap: ClarityGap, questionId: string): CouncilQuestionData;
|
|
18
32
|
export declare function resolveGapsNonInteractive(gaps: ClarityGap[], projectContext: ProjectContext, raw: string): ClarifiedIntent;
|
|
19
33
|
export declare function getAutofilledOutcome(taskType: TaskType | null, raw?: string): string | null;
|
|
@@ -1,309 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export function detectClarityGaps(raw, taskType, confidence, projectContext) {
|
|
3
|
-
const gaps = [];
|
|
4
|
-
// PIL-L6 fix — debug joins the autofill set. For "fix ci fail" the outcome
|
|
5
|
-
// is trivially "error resolved / pipeline green" and forcing an askcard
|
|
6
|
-
// there produces noise (the user already said "goal: ci green").
|
|
7
|
-
const AUTOFILL_OUTCOME_TYPES = new Set(["analyze", "plan", "documentation", "debug"]);
|
|
8
|
-
if (!canInferOutcome(taskType, raw)) {
|
|
9
|
-
if (taskType && AUTOFILL_OUTCOME_TYPES.has(taskType)) {
|
|
10
|
-
// These task types have predictable outcomes — auto-fill without asking
|
|
11
|
-
}
|
|
12
|
-
else if (!taskType || taskType === "general") {
|
|
13
|
-
// B2 intent-swallow fix — a `general` (or unclassified) prompt has no
|
|
14
|
-
// task-specific outcome options, so `buildOutcomeOptions` falls back to
|
|
15
|
-
// the tautological ["Task completed", "Issue resolved"]. Asking that
|
|
16
|
-
// askcard adds zero signal, and its default answer overwrites the intent
|
|
17
|
-
// → "general: Task completed", discarding the user's original request.
|
|
18
|
-
// Skip it; the outcome defaults to the raw prompt downstream
|
|
19
|
-
// (buildClarifiedIntentFromAnswers / getDefaultOutcome), preserving intent.
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
const outcomeOptions = buildOutcomeOptions(taskType, projectContext);
|
|
23
|
-
gaps.push({
|
|
24
|
-
dimension: "outcome",
|
|
25
|
-
description: "Cannot infer the expected outcome from the prompt",
|
|
26
|
-
suggestedQuestion: `What's the expected outcome? ${taskType === "debug" ? "(e.g., error gone, test passes, behavior fixed)" : "(e.g., feature works, file updated, test passes)"}`,
|
|
27
|
-
options: outcomeOptions,
|
|
28
|
-
defaultIndex: pickBestOutcomeIndex(taskType, outcomeOptions, raw),
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
// PIL-L6 fix — operational scope (CI / build / deploy / lint) is enough
|
|
33
|
-
// even without a file path. The task's target is the pipeline itself.
|
|
34
|
-
//
|
|
35
|
-
// B2-symmetric scope guard — the scope detector assumes EVERY prompt is a
|
|
36
|
-
// codebase task: any prompt lacking a file/module/operational reference gets
|
|
37
|
-
// asked "Which part of the codebase should this target?". For a general or
|
|
38
|
-
// unclassified prompt that has no codebase dimension at all (e.g. a pure
|
|
39
|
-
// chat / generation request like "Reply with one word: PONG", live session
|
|
40
|
-
// 8a87aa060c6a) this question is nonsensical, and because it is the only gap
|
|
41
|
-
// it also drags in a downstream acceptance card. Skip it for general/null —
|
|
42
|
-
// the same population the B2 outcome guard above protects. Scope then falls
|
|
43
|
-
// back to project-root in resolveGapsNonInteractive. Classified code tasks
|
|
44
|
-
// (debug/generate/refactor/…) still get the scope-narrowing askcard.
|
|
45
|
-
// Image-scope guard — an image-analysis task (e.g. "analyze diagram.png",
|
|
46
|
-
// "take a screenshot and describe it") is scoped to the IMAGE, not the
|
|
47
|
-
// codebase, so the "Which part of the codebase?" askcard is nonsensical for
|
|
48
|
-
// it. Symmetric to hasOperationalScope (pipeline-scoped). hasImageScope is
|
|
49
|
-
// deliberately narrow so it never swallows a real codebase task.
|
|
50
|
-
const scopeAppliesToCodebase = !!taskType && taskType !== "general";
|
|
51
|
-
if (scopeAppliesToCodebase &&
|
|
52
|
-
countFileReferences(raw) === 0 &&
|
|
53
|
-
!hasExplicitScope(raw) &&
|
|
54
|
-
!hasOperationalScope(raw) &&
|
|
55
|
-
!hasImageScope(raw) &&
|
|
56
|
-
!hasExternalInfoScope(raw) &&
|
|
57
|
-
// Whole-repo / eval prompts ("đánh giá repo", "review the entire codebase")
|
|
58
|
-
// are already scoped to everything — asking "which part?" (and recommending
|
|
59
|
-
// a narrow subdir as default) is nonsensical. See hasWholeRepoScope.
|
|
60
|
-
!hasWholeRepoScope(raw) &&
|
|
61
|
-
// Self-contained computation ("Compute f([3,1,2]) …") supplies its operand
|
|
62
|
-
// data inline — there is no codebase to scope. See hasSelfContainedComputationScope.
|
|
63
|
-
!hasSelfContainedComputationScope(raw)) {
|
|
64
|
-
const scopeOptions = buildScopeOptions(raw, projectContext);
|
|
65
|
-
gaps.push({
|
|
66
|
-
dimension: "scope",
|
|
67
|
-
description: "No specific file or module referenced",
|
|
68
|
-
suggestedQuestion: "Which part of the codebase should this target?",
|
|
69
|
-
options: scopeOptions,
|
|
70
|
-
defaultIndex: pickBestScopeIndex(raw, scopeOptions),
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
const hasConstraint = /\b(\d+\s*ms|\d+\s*%|faster|slower|before|deadline|limit|max|min)\b/i.test(raw);
|
|
74
|
-
const isPerformanceTask = /\b(optimi[zs]e|performance|speed|fast|slow|latency|throughput)\b/i.test(raw);
|
|
75
|
-
if (isPerformanceTask && !hasConstraint) {
|
|
76
|
-
gaps.push({
|
|
77
|
-
dimension: "constraint",
|
|
78
|
-
description: "Performance target not specified",
|
|
79
|
-
suggestedQuestion: "Any specific performance target? (e.g., <200ms response, 50% faster)",
|
|
80
|
-
options: ["General improvement", "Specific latency target", "Reduce bundle size"],
|
|
81
|
-
defaultIndex: 0,
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
return gaps;
|
|
85
|
-
}
|
|
1
|
+
import { hasOperationalScope } from "./clarity-gate.js";
|
|
86
2
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* mentioned tests. This picks a more relevant option based on prompt
|
|
94
|
-
* keywords, with a fallback to 0 when nothing matches.
|
|
95
|
-
*
|
|
96
|
-
* Keep this list short — overengineering breaks predictability. We only
|
|
97
|
-
* encode the keyword→index pairs we've actually seen mismatch in the
|
|
98
|
-
* 5-baseline + sanity sessions.
|
|
3
|
+
* The default "no specific answer" meta-option offered for a model-generated
|
|
4
|
+
* clarification when the model supplies no concrete recommendations. Selecting
|
|
5
|
+
* it means "use your judgment / I have nothing specific to add" — it is a
|
|
6
|
+
* sentinel, NOT a real outcome, so it must never surface verbatim as the
|
|
7
|
+
* resolved outcome. Centralised here so discovery.ts (which presents the
|
|
8
|
+
* option) and the outcome-resolution paths agree on the exact strings.
|
|
99
9
|
*/
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// "improve coverage", "add tests", "viết test" → "Tests added"
|
|
109
|
-
if (has(/\b(coverage|unit test|viết test|viet test|spec|jest|vitest|pytest)\b/) || has(/\btest(?:s|ing)?\b/)) {
|
|
110
|
-
const idx = find("test");
|
|
111
|
-
if (idx >= 0)
|
|
112
|
-
return idx;
|
|
113
|
-
}
|
|
114
|
-
// "scaffold", "boilerplate", "tạo file mới" → "File created with boilerplate"
|
|
115
|
-
if (has(/\b(scaffold|boilerplate|template|skeleton)\b/) || has(/\btạo file\b|\btao file\b/)) {
|
|
116
|
-
const idx = find("file created");
|
|
117
|
-
if (idx >= 0)
|
|
118
|
-
return idx;
|
|
119
|
-
}
|
|
120
|
-
return 0; // "Feature implemented and working"
|
|
121
|
-
}
|
|
122
|
-
case "refactor": {
|
|
123
|
-
// "performance", "speed", "faster" → "Better performance"
|
|
124
|
-
if (has(/\b(performance|speed|fast(er)?|slow|latency|throughput|optimi[zs]e)\b/)) {
|
|
125
|
-
const idx = find("performance");
|
|
126
|
-
if (idx >= 0)
|
|
127
|
-
return idx;
|
|
128
|
-
}
|
|
129
|
-
// "test", "testable" → "Easier to test"
|
|
130
|
-
if (has(/\b(testable|easier to test|unit test)\b/)) {
|
|
131
|
-
const idx = find("test");
|
|
132
|
-
if (idx >= 0)
|
|
133
|
-
return idx;
|
|
134
|
-
}
|
|
135
|
-
return 0; // "Code cleaner, same behavior"
|
|
136
|
-
}
|
|
137
|
-
case "debug": {
|
|
138
|
-
// "test fail", "test pass" → "Test passes"
|
|
139
|
-
if (has(/\btest(?:s|ing)? (?:fail|pass)/) || has(/\bspec fail/)) {
|
|
140
|
-
const idx = find("test passes");
|
|
141
|
-
if (idx >= 0)
|
|
142
|
-
return idx;
|
|
143
|
-
}
|
|
144
|
-
return 0; // "Error disappears"
|
|
145
|
-
}
|
|
146
|
-
case "documentation": {
|
|
147
|
-
if (has(/\b(readme)\b/)) {
|
|
148
|
-
const idx = find("readme");
|
|
149
|
-
if (idx >= 0)
|
|
150
|
-
return idx;
|
|
151
|
-
}
|
|
152
|
-
if (has(/\b(api docs|api documentation|openapi|swagger)\b/)) {
|
|
153
|
-
const idx = find("api docs");
|
|
154
|
-
if (idx >= 0)
|
|
155
|
-
return idx;
|
|
156
|
-
}
|
|
157
|
-
return 0;
|
|
158
|
-
}
|
|
159
|
-
case "plan": {
|
|
160
|
-
if (has(/\b(trade-?offs?|alternative|compare)\b/)) {
|
|
161
|
-
const idx = find("trade");
|
|
162
|
-
if (idx >= 0)
|
|
163
|
-
return idx;
|
|
164
|
-
}
|
|
165
|
-
if (has(/\b(step.?by.?step|phase|roadmap)\b/)) {
|
|
166
|
-
const idx = find("step-by-step");
|
|
167
|
-
if (idx >= 0)
|
|
168
|
-
return idx;
|
|
169
|
-
}
|
|
170
|
-
return 0;
|
|
171
|
-
}
|
|
172
|
-
case "analyze": {
|
|
173
|
-
if (has(/\b(root cause|why|tại sao|tai sao|crash|stack trace)\b/)) {
|
|
174
|
-
const idx = find("root cause");
|
|
175
|
-
if (idx >= 0)
|
|
176
|
-
return idx;
|
|
177
|
-
}
|
|
178
|
-
if (has(/\b(recommend|suggest|đề xuất|de xuat)\b/)) {
|
|
179
|
-
const idx = find("recommendations");
|
|
180
|
-
if (idx >= 0)
|
|
181
|
-
return idx;
|
|
182
|
-
}
|
|
183
|
-
return 0;
|
|
184
|
-
}
|
|
185
|
-
default:
|
|
186
|
-
return 0;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
function buildOutcomeOptions(taskType, ctx) {
|
|
190
|
-
switch (taskType) {
|
|
191
|
-
case "debug":
|
|
192
|
-
return ["Error disappears", "Test passes", "Feature works correctly"];
|
|
193
|
-
case "refactor":
|
|
194
|
-
return ["Code cleaner, same behavior", "Better performance", "Easier to test"];
|
|
195
|
-
case "generate":
|
|
196
|
-
return ["Feature implemented and working", "File created with boilerplate", "Tests added"];
|
|
197
|
-
case "documentation":
|
|
198
|
-
return ["Docs updated", "README reflects current state", "API docs generated"];
|
|
199
|
-
case "plan":
|
|
200
|
-
return ["Architecture decided", "Step-by-step plan", "Trade-offs documented"];
|
|
201
|
-
case "analyze":
|
|
202
|
-
return ["Root cause identified", "Report generated", "Recommendations listed"];
|
|
203
|
-
default:
|
|
204
|
-
return ["Task completed", "Issue resolved"];
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Pick the "Recommended" default index for the scope askcard.
|
|
209
|
-
*
|
|
210
|
-
* Bug fixed (live obs 2026-06-04, deepseek session): the scope gap hardcoded
|
|
211
|
-
* defaultIndex 0, but buildScopeOptions lists recency-ranked (NOT prompt-matched)
|
|
212
|
-
* bounded contexts first when nothing matched — so the card recommended an
|
|
213
|
-
* arbitrary subdir (e.g. "src/cli") for a repo-wide prompt while "Entire project"
|
|
214
|
-
* was demoted to last. Only recommend a specific bounded context when the prompt
|
|
215
|
-
* literally names it (same word-overlap test buildScopeOptions uses); otherwise
|
|
216
|
-
* recommend "Entire project".
|
|
217
|
-
*/
|
|
218
|
-
export function pickBestScopeIndex(raw, options) {
|
|
219
|
-
const entireIdx = options.findIndex((o) => /entire project/i.test(o));
|
|
220
|
-
const fallback = entireIdx >= 0 ? entireIdx : Math.max(0, options.length - 1);
|
|
221
|
-
const words = raw
|
|
222
|
-
.toLowerCase()
|
|
223
|
-
.split(/\s+/)
|
|
224
|
-
.filter((w) => w.length > 2);
|
|
225
|
-
for (let i = 0; i < options.length; i++) {
|
|
226
|
-
const opt = options[i] ?? "";
|
|
227
|
-
if (/entire project/i.test(opt))
|
|
228
|
-
continue;
|
|
229
|
-
const nameMatch = opt.match(/\(([^)]+)\)\s*$/);
|
|
230
|
-
const name = (nameMatch?.[1] ?? opt).toLowerCase();
|
|
231
|
-
if (words.some((w) => name.includes(w) || w.includes(name)))
|
|
232
|
-
return i;
|
|
233
|
-
}
|
|
234
|
-
return fallback;
|
|
235
|
-
}
|
|
236
|
-
function buildScopeOptions(raw, ctx) {
|
|
237
|
-
const words = raw
|
|
238
|
-
.toLowerCase()
|
|
239
|
-
.split(/\s+/)
|
|
240
|
-
.filter((w) => w.length > 2);
|
|
241
|
-
const matching = ctx.boundedContexts.filter((bc) => {
|
|
242
|
-
const name = bc.name.toLowerCase();
|
|
243
|
-
return words.some((w) => name.includes(w) || w.includes(name));
|
|
244
|
-
});
|
|
245
|
-
const options = matching.map((bc) => `${bc.path} (${bc.name})`);
|
|
246
|
-
if (options.length === 0 && ctx.boundedContexts.length > 0) {
|
|
247
|
-
// Phase 5 F4 — when no keyword matches a module name, the previous
|
|
248
|
-
// fallback returned the first 3 alphabetically (which on muonroi-cli
|
|
249
|
-
// surfaced `agent-harness`, `billing`, `chat` — three OLD scaffolding
|
|
250
|
-
// folders that almost never match a fresh prompt). Rank by recency
|
|
251
|
-
// signal instead: most recently modified module dir comes first.
|
|
252
|
-
const ranked = rankModulesByRecency(ctx.boundedContexts, ctx.cwd);
|
|
253
|
-
options.push(...ranked.slice(0, 3).map((bc) => `${bc.path} (${bc.name})`));
|
|
254
|
-
}
|
|
255
|
-
options.push("Entire project");
|
|
256
|
-
return options.slice(0, 4);
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* F4 — rank bounded contexts by recency-of-modification of any tracked file
|
|
260
|
-
* inside. Falls back to alphabetical order when stat() throws for the dir.
|
|
261
|
-
* The 4-level depth cap + 50-entry-per-level cap keeps the walk under 200ms
|
|
262
|
-
* even on huge monorepos.
|
|
263
|
-
*/
|
|
264
|
-
function rankModulesByRecency(contexts, cwd) {
|
|
265
|
-
const fs = require("node:fs");
|
|
266
|
-
const path = require("node:path");
|
|
267
|
-
const scored = contexts.map((bc) => {
|
|
268
|
-
const dirPath = path.join(cwd, bc.path);
|
|
269
|
-
let maxMtime = 0;
|
|
270
|
-
try {
|
|
271
|
-
// walk up to 4 levels deep, cap entries per level
|
|
272
|
-
const walk = (dir, depth) => {
|
|
273
|
-
if (depth > 4)
|
|
274
|
-
return;
|
|
275
|
-
let entries = [];
|
|
276
|
-
try {
|
|
277
|
-
entries = fs.readdirSync(dir).slice(0, 50);
|
|
278
|
-
}
|
|
279
|
-
catch {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
for (const e of entries) {
|
|
283
|
-
if (e.startsWith(".") || e === "node_modules" || e === "dist")
|
|
284
|
-
continue;
|
|
285
|
-
try {
|
|
286
|
-
const full = path.join(dir, e);
|
|
287
|
-
const st = fs.statSync(full);
|
|
288
|
-
if (st.mtimeMs > maxMtime)
|
|
289
|
-
maxMtime = st.mtimeMs;
|
|
290
|
-
if (st.isDirectory())
|
|
291
|
-
walk(full, depth + 1);
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
/* skip unreadable entries */
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
};
|
|
298
|
-
walk(dirPath, 0);
|
|
299
|
-
}
|
|
300
|
-
catch {
|
|
301
|
-
/* fall back to mtime=0 — keeps the entry at the bottom of the ranked list */
|
|
302
|
-
}
|
|
303
|
-
return { bc, mtime: maxMtime };
|
|
304
|
-
});
|
|
305
|
-
scored.sort((a, b) => b.mtime - a.mtime);
|
|
306
|
-
return scored.map((s) => s.bc);
|
|
10
|
+
export const PROVIDE_OWN_DETAILS_OPTION_EN = "I will provide my own details / constraints";
|
|
11
|
+
export const PROVIDE_OWN_DETAILS_OPTION_VI = "Tôi sẽ trả lời tự do / cung cấp chi tiết cần thiết";
|
|
12
|
+
/** True when an answer is the "I'll provide my own details" meta-option (any locale). */
|
|
13
|
+
export function isProvideOwnDetailsSentinel(answer) {
|
|
14
|
+
if (!answer)
|
|
15
|
+
return false;
|
|
16
|
+
const norm = answer.trim().toLowerCase();
|
|
17
|
+
return norm === PROVIDE_OWN_DETAILS_OPTION_EN.toLowerCase() || norm === PROVIDE_OWN_DETAILS_OPTION_VI.toLowerCase();
|
|
307
18
|
}
|
|
308
19
|
export function buildInterviewQuestion(gap, questionId) {
|
|
309
20
|
const options = gap.options.map((label) => ({
|
|
@@ -335,7 +46,9 @@ export function resolveGapsNonInteractive(gaps, projectContext, raw) {
|
|
|
335
46
|
const defaultAnswer = gap.options[gap.defaultIndex] ?? gap.options[0] ?? "";
|
|
336
47
|
switch (gap.dimension) {
|
|
337
48
|
case "outcome":
|
|
338
|
-
|
|
49
|
+
// The "provide my own details" meta-option is a no-answer sentinel —
|
|
50
|
+
// leave outcome empty so the inferred/default outcome is used downstream.
|
|
51
|
+
outcome = isProvideOwnDetailsSentinel(defaultAnswer) ? "" : defaultAnswer;
|
|
339
52
|
break;
|
|
340
53
|
case "scope": {
|
|
341
54
|
const relevant = projectContext.relevantModules.map((m) => m.path);
|
|
@@ -377,7 +90,7 @@ export function getAutofilledOutcome(taskType, raw) {
|
|
|
377
90
|
// Prevents generic "Local path...", "In prompts/ directory...", "Complete the task..." in [Discovery]
|
|
378
91
|
return "Native self-assessment of the CLI with specific, actionable code fixes proposed and verified";
|
|
379
92
|
}
|
|
380
|
-
//
|
|
93
|
+
// Operational debug tasks (CI/build/deploy) have a stronger default outcome.
|
|
381
94
|
if (taskType === "debug" && hasOperationalScope(raw)) {
|
|
382
95
|
return "Pipeline green, all checks passing";
|
|
383
96
|
}
|
|
@@ -23,7 +23,7 @@ import { detectGrayAreas } from "../gsd/gray-areas.js";
|
|
|
23
23
|
import { detectGsdPhase } from "../gsd/types.js";
|
|
24
24
|
import { classifyEeError, logEeFailure } from "../utils/ee-logger.js";
|
|
25
25
|
import { truncateToBudget } from "./budget.js";
|
|
26
|
-
import { isMetaAnalysisPrompt } from "./layer6-output.js";
|
|
26
|
+
import { isImplementationIntent, isMetaAnalysisPrompt, isQuestionLike } from "./layer6-output.js";
|
|
27
27
|
function mapRouteToPhase(route) {
|
|
28
28
|
switch (route) {
|
|
29
29
|
case "qc-flow":
|
|
@@ -79,11 +79,23 @@ export async function layer4Gsd(ctx) {
|
|
|
79
79
|
}
|
|
80
80
|
const complexity = scoreComplexity(ctx.raw);
|
|
81
81
|
const grayAreas = complexity.tier === "heavy" ? detectGrayAreas(ctx.raw).questions : [];
|
|
82
|
-
// Informational
|
|
83
|
-
//
|
|
84
|
-
// human-facing reply as a "2-3 line plan" + process narration
|
|
85
|
-
// 829a83888dd2). Route them to the human-facing question directive
|
|
86
|
-
|
|
82
|
+
// Informational prompts (a question / explanation / self-eval) ask for an
|
|
83
|
+
// ANSWER, not a code change. The implement/verify directive otherwise leaks
|
|
84
|
+
// into the human-facing reply as a "2-3 line plan" + process narration
|
|
85
|
+
// (session 829a83888dd2). Route them to the human-facing question directive.
|
|
86
|
+
//
|
|
87
|
+
// Phase 2b: when the model classified the deliverable, CONSUME it — an
|
|
88
|
+
// "answer" deliverable IS informational. Only when the model didn't emit one
|
|
89
|
+
// (deliverableKind null → legacy cascade, or the model omitted the word) do
|
|
90
|
+
// we fall back to the legacy regex predicates:
|
|
91
|
+
// 1. isMetaAnalysisPrompt — self/CLI evaluation, prior-turn reflection.
|
|
92
|
+
// 2. taskType "general" classified as a real task by L1.
|
|
93
|
+
// 3. question-shaped prompt that is NOT an implementation request.
|
|
94
|
+
const informational = ctx.deliverableKind
|
|
95
|
+
? ctx.deliverableKind === "answer"
|
|
96
|
+
: isMetaAnalysisPrompt(ctx.raw) ||
|
|
97
|
+
(ctx.taskType === "general" && ctx.intentKind === "task") ||
|
|
98
|
+
(isQuestionLike(ctx.raw) && !isImplementationIntent(ctx.raw));
|
|
87
99
|
const directive = buildDirective({ complexity, phase, grayAreas, informational });
|
|
88
100
|
const budgetChars = Math.floor(ctx.tokenBudget * DIRECTIVE_BUDGET_FRACTION);
|
|
89
101
|
const trimmed = truncateToBudget(directive.text, budgetChars);
|
|
@@ -18,5 +18,7 @@ import type { PipelineContext } from "./types.js";
|
|
|
18
18
|
export declare function isMetaAnalysisPrompt(raw: string): boolean;
|
|
19
19
|
export declare function applyPilSuffix(systemPrompt: string, ctx: PipelineContext, responseToolsActive?: boolean): string;
|
|
20
20
|
export declare function isImplementationIntent(raw: string): boolean;
|
|
21
|
+
export declare function isQuestionLike(raw: string): boolean;
|
|
22
|
+
export declare function prefersStructuredReport(raw: string): boolean;
|
|
21
23
|
export declare function getResponseToolSet(ctx: PipelineContext, providerId?: ProviderId): ToolSet;
|
|
22
24
|
export declare function layer6Output(ctx: PipelineContext): Promise<PipelineContext>;
|