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.
Files changed (194) 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/artifact-cache.d.ts +56 -0
  26. package/dist/src/ee/artifact-cache.js +155 -0
  27. package/dist/src/ee/artifact-cache.test.d.ts +1 -0
  28. package/dist/src/ee/artifact-cache.test.js +69 -0
  29. package/dist/src/ee/auth.d.ts +9 -0
  30. package/dist/src/ee/auth.js +19 -0
  31. package/dist/src/ee/ee-onboarding.d.ts +5 -0
  32. package/dist/src/ee/ee-onboarding.js +76 -0
  33. package/dist/src/ee/search.js +7 -5
  34. package/dist/src/ee/search.test.d.ts +1 -0
  35. package/dist/src/ee/search.test.js +23 -0
  36. package/dist/src/generated/version.d.ts +1 -1
  37. package/dist/src/generated/version.js +1 -1
  38. package/dist/src/headless/output.js +6 -4
  39. package/dist/src/headless/output.test.js +4 -3
  40. package/dist/src/index.js +20 -1
  41. package/dist/src/mcp/__tests__/auto-setup.test.js +74 -0
  42. package/dist/src/mcp/__tests__/client-pool.spec.d.ts +1 -0
  43. package/dist/src/mcp/__tests__/client-pool.spec.js +98 -0
  44. package/dist/src/mcp/__tests__/parallel-build.spec.d.ts +1 -0
  45. package/dist/src/mcp/__tests__/parallel-build.spec.js +67 -0
  46. package/dist/src/mcp/__tests__/smart-filter.test.js +56 -0
  47. package/dist/src/mcp/auto-setup.js +56 -2
  48. package/dist/src/mcp/client-pool.d.ts +46 -0
  49. package/dist/src/mcp/client-pool.js +212 -0
  50. package/dist/src/mcp/oauth-callback.js +2 -2
  51. package/dist/src/mcp/parse-headers.test.js +14 -14
  52. package/dist/src/mcp/runtime.d.ts +28 -0
  53. package/dist/src/mcp/runtime.js +117 -51
  54. package/dist/src/mcp/self-verify-runner.d.ts +14 -0
  55. package/dist/src/mcp/self-verify-runner.js +38 -0
  56. package/dist/src/mcp/setup-guide-text.d.ts +9 -0
  57. package/dist/src/mcp/setup-guide-text.js +84 -0
  58. package/dist/src/mcp/smart-filter.js +49 -0
  59. package/dist/src/mcp/smoke.test.js +43 -43
  60. package/dist/src/mcp/tools-server.d.ts +7 -0
  61. package/dist/src/mcp/tools-server.js +19 -22
  62. package/dist/src/models/catalog.json +349 -349
  63. package/dist/src/ops/__tests__/doctor-ee-health.test.js +21 -0
  64. package/dist/src/ops/doctor.d.ts +3 -2
  65. package/dist/src/ops/doctor.js +47 -11
  66. package/dist/src/ops/doctor.test.js +4 -3
  67. package/dist/src/orchestrator/__tests__/mcp-capability-block.test.d.ts +1 -0
  68. package/dist/src/orchestrator/__tests__/mcp-capability-block.test.js +39 -0
  69. package/dist/src/orchestrator/__tests__/project-stack.test.d.ts +1 -0
  70. package/dist/src/orchestrator/__tests__/project-stack.test.js +65 -0
  71. package/dist/src/orchestrator/batch-turn-runner.js +7 -11
  72. package/dist/src/orchestrator/compaction.d.ts +2 -0
  73. package/dist/src/orchestrator/compaction.js +14 -1
  74. package/dist/src/orchestrator/compaction.test.js +25 -1
  75. package/dist/src/orchestrator/message-processor.js +72 -32
  76. package/dist/src/orchestrator/orchestrator.js +26 -0
  77. package/dist/src/orchestrator/prompts.d.ts +51 -0
  78. package/dist/src/orchestrator/prompts.js +257 -134
  79. package/dist/src/orchestrator/scope-ceiling.js +6 -1
  80. package/dist/src/orchestrator/scope-reminder.d.ts +12 -0
  81. package/dist/src/orchestrator/scope-reminder.js +16 -0
  82. package/dist/src/orchestrator/scope-reminder.test.js +22 -1
  83. package/dist/src/orchestrator/stream-runner.js +23 -15
  84. package/dist/src/orchestrator/subagent-compactor.d.ts +14 -5
  85. package/dist/src/orchestrator/subagent-compactor.js +30 -8
  86. package/dist/src/orchestrator/subagent-compactor.spec.js +18 -0
  87. package/dist/src/orchestrator/text-tool-call-detector.test.js +13 -13
  88. package/dist/src/pil/__tests__/clarity-gate.test.js +24 -215
  89. package/dist/src/pil/__tests__/config.test.js +1 -17
  90. package/dist/src/pil/__tests__/discovery.test.js +144 -11
  91. package/dist/src/pil/__tests__/layer1-intent-trace.test.js +7 -2
  92. package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
  93. package/dist/src/pil/__tests__/layer16-clarity.test.js +32 -116
  94. package/dist/src/pil/__tests__/layer4-gsd.test.js +37 -0
  95. package/dist/src/pil/__tests__/layer6-output.test.js +158 -18
  96. package/dist/src/pil/__tests__/llm-classify.test.js +49 -2
  97. package/dist/src/pil/__tests__/surface-compaction-artifacts.test.d.ts +1 -0
  98. package/dist/src/pil/__tests__/surface-compaction-artifacts.test.js +112 -0
  99. package/dist/src/pil/agent-operating-contract.d.ts +1 -1
  100. package/dist/src/pil/agent-operating-contract.js +2 -0
  101. package/dist/src/pil/agent-operating-contract.test.js +7 -2
  102. package/dist/src/pil/cheap-model-playbook.js +35 -35
  103. package/dist/src/pil/cheap-model-workbooks.js +16 -13
  104. package/dist/src/pil/clarity-gate.d.ts +21 -19
  105. package/dist/src/pil/clarity-gate.js +26 -153
  106. package/dist/src/pil/config.d.ts +9 -1
  107. package/dist/src/pil/config.js +15 -4
  108. package/dist/src/pil/discovery.js +211 -136
  109. package/dist/src/pil/layer1-intent.d.ts +12 -0
  110. package/dist/src/pil/layer1-intent.js +283 -38
  111. package/dist/src/pil/layer1-intent.test.js +210 -4
  112. package/dist/src/pil/layer16-clarity.d.ts +25 -11
  113. package/dist/src/pil/layer16-clarity.js +19 -306
  114. package/dist/src/pil/layer3-ee-injection.d.ts +19 -0
  115. package/dist/src/pil/layer3-ee-injection.js +96 -4
  116. package/dist/src/pil/layer4-gsd.js +18 -6
  117. package/dist/src/pil/layer6-output.d.ts +2 -0
  118. package/dist/src/pil/layer6-output.js +151 -25
  119. package/dist/src/pil/llm-classify.d.ts +26 -0
  120. package/dist/src/pil/llm-classify.js +34 -5
  121. package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
  122. package/dist/src/pil/native-capabilities-workbook.js +82 -76
  123. package/dist/src/pil/pipeline.js +15 -9
  124. package/dist/src/pil/schema.d.ts +8 -0
  125. package/dist/src/pil/schema.js +12 -1
  126. package/dist/src/pil/task-tier-map.js +4 -0
  127. package/dist/src/pil/types.d.ts +11 -1
  128. package/dist/src/product-loop/done-gate.js +3 -3
  129. package/dist/src/product-loop/loop-driver.js +18 -18
  130. package/dist/src/product-loop/progress-snapshot.js +4 -4
  131. package/dist/src/providers/auth/gemini-oauth.js +6 -15
  132. package/dist/src/providers/auth/grok-oauth.js +6 -15
  133. package/dist/src/providers/auth/openai-oauth.js +6 -15
  134. package/dist/src/providers/mcp-vision-bridge.js +48 -48
  135. package/dist/src/reporter/index.js +1 -1
  136. package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
  137. package/dist/src/scaffold/bb-quality-gate.js +5 -5
  138. package/dist/src/scaffold/continuation-prompt.js +60 -60
  139. package/dist/src/scaffold/init-new.js +453 -453
  140. package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
  141. package/dist/src/self-qa/agentic-loop.js +24 -19
  142. package/dist/src/self-qa/spec-emitter.js +26 -23
  143. package/dist/src/storage/__tests__/migrations.test.js +2 -2
  144. package/dist/src/storage/interaction-log.js +5 -5
  145. package/dist/src/storage/migrations.js +122 -122
  146. package/dist/src/storage/sessions.js +42 -42
  147. package/dist/src/storage/transcript.js +91 -84
  148. package/dist/src/storage/usage.js +14 -14
  149. package/dist/src/storage/workspaces.js +12 -12
  150. package/dist/src/tools/__tests__/native-tools.test.d.ts +1 -0
  151. package/dist/src/tools/__tests__/native-tools.test.js +53 -0
  152. package/dist/src/tools/git-safety.d.ts +61 -0
  153. package/dist/src/tools/git-safety.js +141 -0
  154. package/dist/src/tools/git-safety.test.d.ts +1 -0
  155. package/dist/src/tools/git-safety.test.js +111 -0
  156. package/dist/src/tools/native-tools.d.ts +31 -0
  157. package/dist/src/tools/native-tools.js +273 -0
  158. package/dist/src/tools/registry-ee-query.test.js +18 -1
  159. package/dist/src/tools/registry-git-safety.test.d.ts +7 -0
  160. package/dist/src/tools/registry-git-safety.test.js +92 -0
  161. package/dist/src/tools/registry.js +52 -6
  162. package/dist/src/ui/__tests__/markdown-render.test.d.ts +1 -0
  163. package/dist/src/ui/__tests__/markdown-render.test.js +48 -0
  164. package/dist/src/ui/app.js +0 -0
  165. package/dist/src/ui/components/message-view.js +4 -1
  166. package/dist/src/ui/components/structured-response-view.js +7 -3
  167. package/dist/src/ui/components/tool-group.js +7 -1
  168. package/dist/src/ui/markdown-render.d.ts +41 -0
  169. package/dist/src/ui/markdown-render.js +223 -0
  170. package/dist/src/ui/markdown.d.ts +10 -0
  171. package/dist/src/ui/markdown.js +12 -35
  172. package/dist/src/ui/slash/council-inspect.js +4 -4
  173. package/dist/src/ui/slash/export.js +4 -4
  174. package/dist/src/ui/utils/text.d.ts +8 -0
  175. package/dist/src/ui/utils/text.js +16 -0
  176. package/dist/src/ui/utils/text.test.d.ts +1 -0
  177. package/dist/src/ui/utils/text.test.js +23 -0
  178. package/dist/src/usage/ledger.js +48 -15
  179. package/dist/src/utils/__tests__/footprint-gitignore.test.d.ts +1 -0
  180. package/dist/src/utils/__tests__/footprint-gitignore.test.js +50 -0
  181. package/dist/src/utils/clipboard-image.js +23 -23
  182. package/dist/src/utils/open-url.d.ts +56 -0
  183. package/dist/src/utils/open-url.js +58 -0
  184. package/dist/src/utils/open-url.test.d.ts +1 -0
  185. package/dist/src/utils/open-url.test.js +86 -0
  186. package/dist/src/utils/settings.d.ts +12 -0
  187. package/dist/src/utils/settings.js +48 -0
  188. package/dist/src/utils/side-question.js +2 -2
  189. package/dist/src/utils/skills.js +3 -3
  190. package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
  191. package/dist/src/verify/environment.js +2 -1
  192. package/package.json +1 -1
  193. package/dist/src/pil/layer16-clarity.test.js +0 -31
  194. /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
  }
@@ -16,3 +16,22 @@
16
16
  */
17
17
  import type { PipelineContext } from "./types.js";
18
18
  export declare function layer3EeInjection(ctx: PipelineContext): Promise<PipelineContext>;
19
+ /**
20
+ * Issue #4 — meta-turn TARGETED complement to Layer 3's checkpoint arm.
21
+ *
22
+ * Since issue #2, Layer 3 now runs on the meta-analysis path too, so its
23
+ * checkpoint arm already surfaces recent checkpoints/artifacts for the agent.
24
+ * That arm uses a FIXED recency query, though — it isn't biased toward the
25
+ * current meta question. This arm fills that gap: it searches by `ctx.raw` so a
26
+ * self-evaluating agent sees the elided tool-artifacts RELEVANT to what it's
27
+ * analyzing, rendered via the same `formatTaskCheckpoints` so the `[artifact]
28
+ * … id=X` refs appear automatically instead of waiting on a manual `ee_query`.
29
+ *
30
+ * Defers to Layer 3: if a checkpoint block was already injected this turn (any
31
+ * `ee-checkpoint-injected` marker present) it skips entirely — no duplicate
32
+ * block and no second EE round-trip. Gated on `sessionId` (no session ⇒ no prior
33
+ * compaction to rehydrate). Strictly additive and fail-open: any error /
34
+ * no-session / no-match / already-surfaced returns ctx with the original
35
+ * `enriched` plus an `ee-meta-artifacts` layer marker for forensics.
36
+ */
37
+ export declare function surfaceCompactionArtifacts(ctx: PipelineContext): Promise<PipelineContext>;
@@ -119,7 +119,7 @@ async function queryEeBridge(raw) {
119
119
  const [principleRaw, behavioralRaw, checkpointRaw] = await Promise.all([
120
120
  searchByText(raw, ["experience-principles"], 3, signal),
121
121
  searchByText(raw, ["experience-behavioral"], 4, signal),
122
- searchByText("Context checkpoint summary OR \"compaction checkpoint\" recent Progress DONE elided OR tool-artifact OR \"tool result id=\"", ["experience-behavioral"], 3, signal).catch(() => []),
122
+ searchByText('Context checkpoint summary OR "compaction checkpoint" recent Progress DONE elided OR tool-artifact OR "tool result id="', ["experience-behavioral"], 3, signal).catch(() => []),
123
123
  ]);
124
124
  const principlePoints = principleRaw.filter((p) => (p.score ?? 0) >= PIL_PRINCIPLES_FLOOR);
125
125
  const behavioralPoints = behavioralRaw.filter((p) => (p.score ?? 0) >= PIL_SCORE_FLOOR);
@@ -161,14 +161,16 @@ function formatExperienceHints(points) {
161
161
  function formatTaskCheckpoints(points) {
162
162
  if (points.length === 0)
163
163
  return "";
164
- const lines = points.map((p) => {
164
+ const lines = points
165
+ .map((p) => {
165
166
  const t = extractPointText(p);
166
167
  // Idea 4: surface tool-artifact refs so agent sees "elided high-value, query for full"
167
168
  if (/tool-artifact|tool result id=|elided.*id=/.test(t.toLowerCase())) {
168
169
  return `- [artifact] ${t.slice(0, 160)} [id:${p.id}]`;
169
170
  }
170
171
  return `- ${t.slice(0, 180)} [id:${p.id}]`;
171
- }).filter((l) => l !== "- ");
172
+ })
173
+ .filter((l) => l !== "- ");
172
174
  if (lines.length === 0)
173
175
  return "";
174
176
  return `[task checkpoints — prior compactions: use to answer "task finished?", "compacted yet?". Artifacts: use ee.query tool with "tool-artifact id=XXX" for full elided tool output.] \n${lines.join("\n")}`;
@@ -282,7 +284,7 @@ export async function layer3EeInjection(ctx) {
282
284
  const text = extractPointText(p);
283
285
  return text.length === 0 || !checkpointMarkerShas.has(payloadSha16(text));
284
286
  })
285
- : (result.checkpointPoints || []);
287
+ : result.checkpointPoints || [];
286
288
  const allPoints = [...deduplicatedPrinciples, ...deduplicatedBehavioral, ...deduplicatedCheckpoints];
287
289
  // STALE-01: Register injected point IDs for prompt-stale reconciliation.
288
290
  updateLastSurfacedState(allPoints.map((p) => String(p.id)));
@@ -359,4 +361,94 @@ export async function layer3EeInjection(ctx) {
359
361
  ],
360
362
  };
361
363
  }
364
+ /**
365
+ * Records whose text actually reads like a compaction checkpoint or an elided
366
+ * tool-artifact. Used to keep generic behavioral hits from being mislabelled as
367
+ * `[artifact]`/checkpoint lines when we search by the meta question (ctx.raw)
368
+ * rather than the fixed checkpoint-arm query.
369
+ */
370
+ const CHECKPOINT_LIKE_RE = /context checkpoint summary|compaction checkpoint|tool-artifact|tool result id=|elided|progress[^a-z]*done|✔/i;
371
+ /**
372
+ * Issue #4 — meta-turn TARGETED complement to Layer 3's checkpoint arm.
373
+ *
374
+ * Since issue #2, Layer 3 now runs on the meta-analysis path too, so its
375
+ * checkpoint arm already surfaces recent checkpoints/artifacts for the agent.
376
+ * That arm uses a FIXED recency query, though — it isn't biased toward the
377
+ * current meta question. This arm fills that gap: it searches by `ctx.raw` so a
378
+ * self-evaluating agent sees the elided tool-artifacts RELEVANT to what it's
379
+ * analyzing, rendered via the same `formatTaskCheckpoints` so the `[artifact]
380
+ * … id=X` refs appear automatically instead of waiting on a manual `ee_query`.
381
+ *
382
+ * Defers to Layer 3: if a checkpoint block was already injected this turn (any
383
+ * `ee-checkpoint-injected` marker present) it skips entirely — no duplicate
384
+ * block and no second EE round-trip. Gated on `sessionId` (no session ⇒ no prior
385
+ * compaction to rehydrate). Strictly additive and fail-open: any error /
386
+ * no-session / no-match / already-surfaced returns ctx with the original
387
+ * `enriched` plus an `ee-meta-artifacts` layer marker for forensics.
388
+ */
389
+ export async function surfaceCompactionArtifacts(ctx) {
390
+ const markLayer = (applied, delta) => ({
391
+ ...ctx,
392
+ layers: [...ctx.layers, { name: "ee-meta-artifacts", applied, delta }],
393
+ });
394
+ if (!ctx.sessionId)
395
+ return markLayer(false, "no-session");
396
+ // Defer to Layer 3: a checkpoint/artifact block is already present this turn,
397
+ // so don't duplicate it or pay a second EE round-trip. This arm only fills the
398
+ // gap when Layer 3's fixed-query checkpoint arm surfaced nothing.
399
+ if (extractCheckpointMarkerShas(ctx.enriched).size > 0)
400
+ return markLayer(false, "already-surfaced");
401
+ let points = [];
402
+ try {
403
+ const signal = AbortSignal.timeout(PIL_SEARCH_TIMEOUT_MS);
404
+ // Bias toward records relevant to THIS meta question (ctx.raw) while pulling
405
+ // in checkpoint/artifact vocabulary so the single cheap arm lands on the
406
+ // compaction records rather than generic behavioral patterns.
407
+ const query = `${ctx.raw}\nContext checkpoint summary tool-artifact "tool result id=" elided Progress DONE`;
408
+ const raw = await searchByText(query, ["experience-behavioral"], 5, signal);
409
+ points = raw
410
+ .filter((p) => (p.score ?? 0) >= PIL_SCORE_FLOOR * 0.7)
411
+ .filter((p) => CHECKPOINT_LIKE_RE.test(extractPointText(p)));
412
+ }
413
+ catch (err) {
414
+ logEeFailure("pil.meta.surfaceCompactionArtifacts", classifyEeError(err), err, { budgetMs: PIL_SEARCH_TIMEOUT_MS });
415
+ return markLayer(false, `error=${String(err)}`);
416
+ }
417
+ if (points.length === 0)
418
+ return markLayer(false, "no-artifacts");
419
+ const cpText = formatTaskCheckpoints(points);
420
+ if (!cpText)
421
+ return markLayer(false, "no-artifacts");
422
+ // Append the marker AFTER truncation so it always survives into `enriched`
423
+ // — that marker is what makes the defer-check above fire on any later pass.
424
+ const blockSha = payloadSha16(cpText);
425
+ const body = truncateToBudget(cpText, Math.floor(ctx.tokenBudget * 0.12));
426
+ const block = `${body}\n<!-- ee-checkpoint-injected:${blockSha} -->`;
427
+ try {
428
+ if (ctx.sessionId) {
429
+ logInteraction(ctx.sessionId, "ee_injection", {
430
+ eventSubtype: "injected",
431
+ data: {
432
+ phase: "pil_meta_artifacts",
433
+ role: "knowledge_retriever",
434
+ checkpointCount: points.length,
435
+ pointIds: points.map((p) => String(p.id)),
436
+ injectedChars: block.length,
437
+ },
438
+ });
439
+ }
440
+ }
441
+ catch (err) {
442
+ // No silent catch: surfacing succeeded; only the audit write failed.
443
+ console.error(`[pil.meta.surfaceCompactionArtifacts] interaction log failed: ${err?.message}`);
444
+ }
445
+ return {
446
+ ...ctx,
447
+ enriched: `${ctx.enriched}\n${block}`,
448
+ layers: [
449
+ ...ctx.layers,
450
+ { name: "ee-meta-artifacts", applied: true, delta: `artifacts=${points.length} chars=${block.length}` },
451
+ ],
452
+ };
453
+ }
362
454
  //# sourceMappingURL=layer3-ee-injection.js.map
@@ -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>;