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.
Files changed (172) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +122 -122
  3. package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
  4. package/dist/src/agent-harness/__tests__/mock-model.spec.js +48 -1
  5. package/dist/src/agent-harness/mock-model.d.ts +11 -0
  6. package/dist/src/agent-harness/mock-model.js +21 -0
  7. package/dist/src/cli/cost-forensics.js +12 -12
  8. package/dist/src/council/__tests__/clarification-prompt.test.js +51 -0
  9. package/dist/src/council/__tests__/clarifier-ready-gate.test.js +32 -0
  10. package/dist/src/council/__tests__/decisions-lock.test.js +17 -1
  11. package/dist/src/council/__tests__/oauth-reachable.test.d.ts +1 -0
  12. package/dist/src/council/__tests__/oauth-reachable.test.js +31 -0
  13. package/dist/src/council/__tests__/parse-outcome-fallback.test.js +11 -0
  14. package/dist/src/council/clarifier.js +9 -1
  15. package/dist/src/council/debate.js +5 -1
  16. package/dist/src/council/decisions-lock.js +3 -3
  17. package/dist/src/council/index.js +12 -5
  18. package/dist/src/council/leader.d.ts +0 -17
  19. package/dist/src/council/leader.js +22 -15
  20. package/dist/src/council/planner.js +1 -1
  21. package/dist/src/council/prompts.js +63 -57
  22. package/dist/src/council/types.d.ts +7 -0
  23. package/dist/src/ee/__tests__/ee-onboarding.test.d.ts +1 -0
  24. package/dist/src/ee/__tests__/ee-onboarding.test.js +32 -0
  25. package/dist/src/ee/auth.d.ts +9 -0
  26. package/dist/src/ee/auth.js +19 -0
  27. package/dist/src/ee/ee-onboarding.d.ts +5 -0
  28. package/dist/src/ee/ee-onboarding.js +76 -0
  29. package/dist/src/generated/version.d.ts +1 -1
  30. package/dist/src/generated/version.js +1 -1
  31. package/dist/src/headless/output.js +6 -4
  32. package/dist/src/headless/output.test.js +4 -3
  33. package/dist/src/index.js +20 -1
  34. package/dist/src/mcp/__tests__/auto-setup.test.js +74 -0
  35. package/dist/src/mcp/__tests__/client-pool.spec.d.ts +1 -0
  36. package/dist/src/mcp/__tests__/client-pool.spec.js +98 -0
  37. package/dist/src/mcp/__tests__/parallel-build.spec.d.ts +1 -0
  38. package/dist/src/mcp/__tests__/parallel-build.spec.js +67 -0
  39. package/dist/src/mcp/__tests__/smart-filter.test.js +56 -0
  40. package/dist/src/mcp/auto-setup.js +56 -2
  41. package/dist/src/mcp/client-pool.d.ts +46 -0
  42. package/dist/src/mcp/client-pool.js +212 -0
  43. package/dist/src/mcp/oauth-callback.js +2 -2
  44. package/dist/src/mcp/parse-headers.test.js +14 -14
  45. package/dist/src/mcp/runtime.d.ts +28 -0
  46. package/dist/src/mcp/runtime.js +117 -51
  47. package/dist/src/mcp/self-verify-runner.d.ts +14 -0
  48. package/dist/src/mcp/self-verify-runner.js +38 -0
  49. package/dist/src/mcp/setup-guide-text.d.ts +9 -0
  50. package/dist/src/mcp/setup-guide-text.js +84 -0
  51. package/dist/src/mcp/smart-filter.js +49 -0
  52. package/dist/src/mcp/smoke.test.js +43 -43
  53. package/dist/src/mcp/tools-server.d.ts +7 -0
  54. package/dist/src/mcp/tools-server.js +19 -22
  55. package/dist/src/models/catalog.json +349 -349
  56. package/dist/src/ops/__tests__/doctor-ee-health.test.js +21 -0
  57. package/dist/src/ops/doctor.d.ts +3 -2
  58. package/dist/src/ops/doctor.js +47 -11
  59. package/dist/src/ops/doctor.test.js +4 -3
  60. package/dist/src/orchestrator/__tests__/mcp-capability-block.test.d.ts +1 -0
  61. package/dist/src/orchestrator/__tests__/mcp-capability-block.test.js +39 -0
  62. package/dist/src/orchestrator/__tests__/project-stack.test.d.ts +1 -0
  63. package/dist/src/orchestrator/__tests__/project-stack.test.js +65 -0
  64. package/dist/src/orchestrator/batch-turn-runner.js +7 -11
  65. package/dist/src/orchestrator/message-processor.js +57 -27
  66. package/dist/src/orchestrator/orchestrator.js +26 -0
  67. package/dist/src/orchestrator/prompts.d.ts +51 -0
  68. package/dist/src/orchestrator/prompts.js +257 -134
  69. package/dist/src/orchestrator/scope-ceiling.js +6 -1
  70. package/dist/src/orchestrator/stream-runner.js +20 -15
  71. package/dist/src/orchestrator/text-tool-call-detector.test.js +13 -13
  72. package/dist/src/pil/__tests__/clarity-gate.test.js +24 -215
  73. package/dist/src/pil/__tests__/config.test.js +1 -17
  74. package/dist/src/pil/__tests__/discovery.test.js +144 -11
  75. package/dist/src/pil/__tests__/layer1-intent-trace.test.js +7 -2
  76. package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
  77. package/dist/src/pil/__tests__/layer16-clarity.test.js +32 -116
  78. package/dist/src/pil/__tests__/layer4-gsd.test.js +37 -0
  79. package/dist/src/pil/__tests__/layer6-output.test.js +137 -18
  80. package/dist/src/pil/__tests__/llm-classify.test.js +49 -2
  81. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  82. package/dist/src/pil/agent-operating-contract.js +2 -0
  83. package/dist/src/pil/agent-operating-contract.test.js +7 -2
  84. package/dist/src/pil/cheap-model-playbook.js +35 -35
  85. package/dist/src/pil/cheap-model-workbooks.js +16 -13
  86. package/dist/src/pil/clarity-gate.d.ts +21 -19
  87. package/dist/src/pil/clarity-gate.js +26 -153
  88. package/dist/src/pil/config.d.ts +9 -1
  89. package/dist/src/pil/config.js +15 -4
  90. package/dist/src/pil/discovery.js +211 -136
  91. package/dist/src/pil/layer1-intent.d.ts +12 -0
  92. package/dist/src/pil/layer1-intent.js +283 -38
  93. package/dist/src/pil/layer1-intent.test.js +210 -4
  94. package/dist/src/pil/layer16-clarity.d.ts +25 -11
  95. package/dist/src/pil/layer16-clarity.js +19 -306
  96. package/dist/src/pil/layer4-gsd.js +18 -6
  97. package/dist/src/pil/layer6-output.d.ts +2 -0
  98. package/dist/src/pil/layer6-output.js +137 -22
  99. package/dist/src/pil/llm-classify.d.ts +26 -0
  100. package/dist/src/pil/llm-classify.js +34 -5
  101. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  102. package/dist/src/pil/native-capabilities-workbook.js +82 -76
  103. package/dist/src/pil/schema.d.ts +8 -0
  104. package/dist/src/pil/schema.js +12 -1
  105. package/dist/src/pil/task-tier-map.js +4 -0
  106. package/dist/src/pil/types.d.ts +11 -1
  107. package/dist/src/product-loop/done-gate.js +3 -3
  108. package/dist/src/product-loop/loop-driver.js +18 -18
  109. package/dist/src/product-loop/progress-snapshot.js +4 -4
  110. package/dist/src/providers/auth/gemini-oauth.js +6 -15
  111. package/dist/src/providers/auth/grok-oauth.js +6 -15
  112. package/dist/src/providers/auth/openai-oauth.js +6 -15
  113. package/dist/src/providers/mcp-vision-bridge.js +48 -48
  114. package/dist/src/reporter/index.js +1 -1
  115. package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
  116. package/dist/src/scaffold/bb-quality-gate.js +5 -5
  117. package/dist/src/scaffold/continuation-prompt.js +60 -60
  118. package/dist/src/scaffold/init-new.js +453 -453
  119. package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
  120. package/dist/src/self-qa/agentic-loop.js +24 -19
  121. package/dist/src/self-qa/spec-emitter.js +26 -23
  122. package/dist/src/storage/__tests__/migrations.test.js +2 -2
  123. package/dist/src/storage/interaction-log.js +5 -5
  124. package/dist/src/storage/migrations.js +122 -122
  125. package/dist/src/storage/sessions.js +42 -42
  126. package/dist/src/storage/transcript.js +91 -84
  127. package/dist/src/storage/usage.js +14 -14
  128. package/dist/src/storage/workspaces.js +12 -12
  129. package/dist/src/tools/__tests__/native-tools.test.d.ts +1 -0
  130. package/dist/src/tools/__tests__/native-tools.test.js +53 -0
  131. package/dist/src/tools/git-safety.d.ts +61 -0
  132. package/dist/src/tools/git-safety.js +141 -0
  133. package/dist/src/tools/git-safety.test.d.ts +1 -0
  134. package/dist/src/tools/git-safety.test.js +111 -0
  135. package/dist/src/tools/native-tools.d.ts +31 -0
  136. package/dist/src/tools/native-tools.js +273 -0
  137. package/dist/src/tools/registry-git-safety.test.d.ts +7 -0
  138. package/dist/src/tools/registry-git-safety.test.js +92 -0
  139. package/dist/src/tools/registry.js +39 -4
  140. package/dist/src/ui/__tests__/markdown-render.test.d.ts +1 -0
  141. package/dist/src/ui/__tests__/markdown-render.test.js +48 -0
  142. package/dist/src/ui/app.js +0 -0
  143. package/dist/src/ui/components/message-view.js +4 -1
  144. package/dist/src/ui/components/structured-response-view.js +7 -3
  145. package/dist/src/ui/components/tool-group.js +7 -1
  146. package/dist/src/ui/markdown-render.d.ts +41 -0
  147. package/dist/src/ui/markdown-render.js +223 -0
  148. package/dist/src/ui/markdown.d.ts +10 -0
  149. package/dist/src/ui/markdown.js +12 -35
  150. package/dist/src/ui/slash/council-inspect.js +4 -4
  151. package/dist/src/ui/slash/export.js +4 -4
  152. package/dist/src/ui/utils/text.d.ts +8 -0
  153. package/dist/src/ui/utils/text.js +16 -0
  154. package/dist/src/ui/utils/text.test.d.ts +1 -0
  155. package/dist/src/ui/utils/text.test.js +23 -0
  156. package/dist/src/usage/ledger.js +48 -15
  157. package/dist/src/utils/__tests__/footprint-gitignore.test.d.ts +1 -0
  158. package/dist/src/utils/__tests__/footprint-gitignore.test.js +50 -0
  159. package/dist/src/utils/clipboard-image.js +23 -23
  160. package/dist/src/utils/open-url.d.ts +56 -0
  161. package/dist/src/utils/open-url.js +58 -0
  162. package/dist/src/utils/open-url.test.d.ts +1 -0
  163. package/dist/src/utils/open-url.test.js +86 -0
  164. package/dist/src/utils/settings.d.ts +12 -0
  165. package/dist/src/utils/settings.js +48 -0
  166. package/dist/src/utils/side-question.js +2 -2
  167. package/dist/src/utils/skills.js +3 -3
  168. package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
  169. package/dist/src/verify/environment.js +2 -1
  170. package/package.json +1 -1
  171. package/dist/src/pil/layer16-clarity.test.js +0 -31
  172. /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
- * Pick the "Recommended" default index for the scope askcard.
7
- *
8
- * Bug fixed (live obs 2026-06-04, deepseek session): the scope gap hardcoded
9
- * defaultIndex 0, but buildScopeOptions lists recency-ranked (NOT prompt-matched)
10
- * bounded contexts first when nothing matched so the card recommended an
11
- * arbitrary subdir (e.g. "src/cli") for a repo-wide prompt while "Entire project"
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 function pickBestScopeIndex(raw: string, options: string[]): number;
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 { canInferOutcome, countFileReferences, hasExplicitScope, hasExternalInfoScope, hasImageScope, hasOperationalScope, hasSelfContainedComputationScope, hasWholeRepoScope, } from "./clarity-gate.js";
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
- * Phase 5 F8 context-aware default option for outcome askcards.
88
- *
89
- * The askcard's "Recommended" badge previously pinned to options[0]
90
- * regardless of prompt content. For prompts like "improve test coverage"
91
- * (generate options: Feature implemented / File created / Tests added),
92
- * defaulting to "Feature implemented" was wrong the user explicitly
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
- function pickBestOutcomeIndex(taskType, options, raw) {
101
- if (options.length <= 1)
102
- return 0;
103
- const lower = raw.toLowerCase();
104
- const has = (re) => re.test(lower);
105
- const find = (substring) => options.findIndex((o) => o.toLowerCase().includes(substring));
106
- switch (taskType) {
107
- case "generate": {
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
- outcome = defaultAnswer;
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
- // PIL-L6 fix — operational debug tasks have a stronger default outcome
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/meta prompts (a question or a self/meta CLI analysis) ask for
83
- // an ANSWER, not a code change. The implement/verify directive leaks into the
84
- // human-facing reply as a "2-3 line plan" + process narration (session
85
- // 829a83888dd2). Route them to the human-facing question directive instead.
86
- const informational = isMetaAnalysisPrompt(ctx.raw) || ctx.taskType === "general";
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>;