@planu/cli 4.10.5 → 4.10.7

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 (50) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/engine/compact/tool-list-compactor.d.ts +1 -1
  3. package/dist/engine/evidence-gates/lifecycle-gate.js +17 -1
  4. package/dist/engine/evidence-index/done-drift.js +4 -1
  5. package/dist/engine/local-first/tool-classification.d.ts +7 -0
  6. package/dist/engine/local-first/tool-classification.js +313 -0
  7. package/dist/engine/qa-gate.js +13 -1
  8. package/dist/engine/scope-boundaries/scope-validator.js +14 -2
  9. package/dist/engine/validator/validation-report-writer.js +3 -4
  10. package/dist/tools/audit.js +1 -1
  11. package/dist/tools/challenge-spec.js +52 -2
  12. package/dist/tools/check-readiness.js +15 -8
  13. package/dist/tools/data-governance/audit-handler.js +21 -6
  14. package/dist/tools/data-governance/detect-handler.js +32 -15
  15. package/dist/tools/define-ui-contract.js +1 -4
  16. package/dist/tools/design-schema.js +1 -4
  17. package/dist/tools/detect-drift.js +48 -28
  18. package/dist/tools/flag-spec-gap.js +47 -12
  19. package/dist/tools/generate-sub-agent.js +2 -4
  20. package/dist/tools/list-specs.js +15 -59
  21. package/dist/tools/orchestrate-locking.js +15 -4
  22. package/dist/tools/output-compressor.d.ts +14 -1
  23. package/dist/tools/output-compressor.js +121 -2
  24. package/dist/tools/package-handoff.js +136 -12
  25. package/dist/tools/reverse-engineer/handler.js +9 -10
  26. package/dist/tools/rollback-release.js +20 -2
  27. package/dist/tools/spec-diff-handler.js +3 -3
  28. package/dist/tools/status-handler.js +6 -1
  29. package/dist/tools/suggest-mcp-server.js +2 -4
  30. package/dist/tools/token-recording.d.ts +2 -1
  31. package/dist/tools/token-recording.js +21 -2
  32. package/dist/tools/update-status/dod-gates.d.ts +2 -2
  33. package/dist/tools/update-status/dod-gates.js +47 -52
  34. package/dist/tools/update-status/evidence-gate.js +6 -4
  35. package/dist/tools/update-status/index.js +10 -3
  36. package/dist/tools/update-status/qa-gate.d.ts +5 -0
  37. package/dist/tools/update-status/qa-gate.js +50 -0
  38. package/dist/tools/update-status/response-builder.js +96 -2
  39. package/dist/tools/update-status/transition-guard.js +33 -11
  40. package/dist/tools/update-status-convention-gate.js +22 -6
  41. package/dist/tools/validate-team-results.js +23 -1
  42. package/dist/tools/validate.js +137 -16
  43. package/dist/transports/http-transport.js +13 -0
  44. package/dist/transports/transport-factory.js +13 -0
  45. package/dist/types/qa-gate.d.ts +3 -0
  46. package/dist/types/tool-classification.d.ts +8 -0
  47. package/dist/types/tool-classification.js +2 -0
  48. package/package.json +10 -10
  49. package/planu-native.json +1 -1
  50. package/planu-plugin.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## [4.10.7] - 2026-07-07
2
+
3
+ ### Bug Fixes
4
+ - fix(SPEC-1114): reduce MCP token waste
5
+
6
+ ### Chores
7
+ - chore(deps): update patch/minor dependencies
8
+ - chore(planu): clear released pending specs
9
+
10
+
11
+ ## [4.10.6] - 2026-07-07
12
+
13
+ ### Bug Fixes
14
+ - fix(SPEC-1111): stabilize update_status done gates and evidence state transitions
15
+ - fix(SPEC-1112): preserve validation-report lint evidence during done gate
16
+
17
+ ### Improvements
18
+ - refactor(SPEC-1110): reduce MCP token payloads with local-first tool classification
19
+
20
+
1
21
  ## [4.10.5] - 2026-07-07
2
22
 
3
23
  ### Bug Fixes
@@ -3,5 +3,5 @@ export declare function compactToolList(tools: unknown[]): Record<string, unknow
3
3
  /** Returns true when an outgoing JSON-RPC message is a tools/list result. */
4
4
  export declare function isToolsListResponse(message: unknown): boolean;
5
5
  /** Compact a tools/list JSON-RPC response message. Returns a new object. */
6
- export declare function compactToolsListMessage(message: Record<string, unknown>): Record<string, unknown>;
6
+ export declare function compactToolsListMessage<T extends Record<string, unknown>>(message: T): T;
7
7
  //# sourceMappingURL=tool-list-compactor.d.ts.map
@@ -114,9 +114,25 @@ export function checkDoneEvidenceGate(spec, criteria, artifacts) {
114
114
  !hasText(row.validationEvidence) ||
115
115
  !hasText(row.reviewerEvidence));
116
116
  if (incompleteRows.length > 0) {
117
+ const rowDetails = incompleteRows.map((row) => {
118
+ const missing = [];
119
+ if (!rowHasEvidence(row)) {
120
+ missing.push('scenario/testEvidence/contractEvidence/manualEvidence');
121
+ }
122
+ if (row.changedFiles.length === 0) {
123
+ missing.push('changedFiles');
124
+ }
125
+ if (!hasText(row.validationEvidence)) {
126
+ missing.push('validationEvidence');
127
+ }
128
+ if (!hasText(row.reviewerEvidence)) {
129
+ missing.push('reviewerEvidence');
130
+ }
131
+ return `${row.acceptanceCriterion}: missing ${missing.join(', ')}`;
132
+ });
117
133
  issues.push({
118
134
  code: 'traceability_incomplete_rows',
119
- message: 'Traceability rows must include scenario/test/contract/manual evidence, changed files, validation evidence, and reviewer evidence.',
135
+ message: `Traceability rows must include evidence, changed files, validation evidence, and reviewer evidence. Incomplete rows: ${rowDetails.join('; ')}`,
120
136
  });
121
137
  }
122
138
  }
@@ -33,9 +33,12 @@ export function checkDoneDriftContract(args) {
33
33
  .map((record) => `${entry.criterion}: ${record.value}`);
34
34
  });
35
35
  if (unapproved.length > 0) {
36
+ const groundedHint = args.groundedTechnicalPaths.length > 0
37
+ ? ` Grounded technical paths: ${args.groundedTechnicalPaths.join(', ')}.`
38
+ : ' No grounded technical paths were found in the approved spec.';
36
39
  issues.push({
37
40
  code: 'done_drift_unapproved_scope',
38
- message: `Done drift check found changed files outside grounded spec scope: ${unapproved.join('; ')}`,
41
+ message: `Done drift check found changed files outside grounded spec scope: ${unapproved.join('; ')}.${groundedHint} Add the file to approved technical references or include reviewerEvidence with an approved scope amendment.`,
39
42
  });
40
43
  }
41
44
  }
@@ -0,0 +1,7 @@
1
+ import type { ToolClassification } from '../../types/tool-classification.js';
2
+ export declare function getToolClassification(toolName: string): ToolClassification;
3
+ export declare function hasExplicitToolClassification(toolName: string): boolean;
4
+ export declare function isLocalFirstTool(toolName: string): boolean;
5
+ export declare function requiresLlmByDefault(toolName: string): boolean;
6
+ export declare function listToolClassifications(): ToolClassification[];
7
+ //# sourceMappingURL=tool-classification.d.ts.map
@@ -0,0 +1,313 @@
1
+ const REGISTERED_TOOL_NAMES = [
2
+ 'agent_run_history',
3
+ 'analyze_spec_dependencies',
4
+ 'approval_status',
5
+ 'approve_spec',
6
+ 'assess_merge_risk',
7
+ 'assign_role',
8
+ 'audit',
9
+ 'audit_claude_config',
10
+ 'audit_log_export',
11
+ 'audit_schema_type_parity',
12
+ 'audit_specs_drift',
13
+ 'bump_spec_version',
14
+ 'challenge_spec',
15
+ 'check_compliance',
16
+ 'check_config_health',
17
+ 'check_spec_accuracy',
18
+ 'check_versions',
19
+ 'clarify_requirements',
20
+ 'code_graph_status',
21
+ 'compliance_coverage_report',
22
+ 'compliance_gap_analysis',
23
+ 'compliance_gate_status',
24
+ 'compliance_score_report',
25
+ 'config_health',
26
+ 'configure_approval_policy',
27
+ 'configure_code_graph',
28
+ 'configure_compliance',
29
+ 'configure_compliance_gate',
30
+ 'configure_memory',
31
+ 'configure_roles',
32
+ 'configure_squad',
33
+ 'configure_workers',
34
+ 'consult_docs',
35
+ 'context_budget',
36
+ 'coverage_gap_analyzer',
37
+ 'create_spec',
38
+ 'data_retention_policy',
39
+ 'define_spec_lint_rule',
40
+ 'define_ui_contract',
41
+ 'delete_spec_lint_rule',
42
+ 'design_schema',
43
+ 'detect_agent',
44
+ 'detect_drift',
45
+ 'discover_docs_url',
46
+ 'discover_mcps',
47
+ 'discover_registry',
48
+ 'ears_lint',
49
+ 'ecosystem_status',
50
+ 'estimate',
51
+ 'eval_rule',
52
+ 'eval_skill',
53
+ 'export_audit_trail',
54
+ 'expose_spec_as_prompt',
55
+ 'fix_schema_type_parity',
56
+ 'free_tool',
57
+ 'generate_adr',
58
+ 'generate_api_client',
59
+ 'generate_checklist',
60
+ 'generate_compliance_report',
61
+ 'generate_compliance_tests',
62
+ 'generate_docs',
63
+ 'generate_docs_site',
64
+ 'generate_execution_plan',
65
+ 'generate_orchestration_script',
66
+ 'generate_rules',
67
+ 'generate_skill',
68
+ 'generate_sub_agent',
69
+ 'generate_tests',
70
+ 'generate_validation_schema',
71
+ 'graph_specs',
72
+ 'guided_tour',
73
+ 'init_constitution',
74
+ 'init_project',
75
+ 'integrate_pm',
76
+ 'issue_reviewer_token',
77
+ 'learn_pattern',
78
+ 'list_spec_lint_rules',
79
+ 'list_spec_prompts',
80
+ 'list_specs',
81
+ 'manage_context',
82
+ 'manage_git',
83
+ 'manage_plugins',
84
+ 'memory_status',
85
+ 'oauth_status',
86
+ 'onboarding_checklist',
87
+ 'onboarding_progress',
88
+ 'orchestrate',
89
+ 'paradigm_report',
90
+ 'pin_version',
91
+ 'pro_tool',
92
+ 'publish_registry',
93
+ 'quick_start',
94
+ 'reconcile_hooks',
95
+ 'reconcile_rules',
96
+ 'reconcile_skills',
97
+ 'reconcile_spec',
98
+ 'red_team',
99
+ 'registry_install',
100
+ 'registry_login',
101
+ 'registry_logout',
102
+ 'registry_publish',
103
+ 'registry_search',
104
+ 'registry_whoami',
105
+ 'request_changes',
106
+ 'reverse_engineer',
107
+ 'rewrite_criteria_ears',
108
+ 'run_mutation_hints',
109
+ 'run_spec_lint',
110
+ 'scaffold_plugin',
111
+ 'scan_orphan_spec_refs',
112
+ 'search_all_projects',
113
+ 'set_locale',
114
+ 'set_work_mode',
115
+ 'skill_install',
116
+ 'skill_search',
117
+ 'spec_obesity_healer',
118
+ 'squad_status',
119
+ 'ssr_back_migration',
120
+ 'start_oauth_flow',
121
+ 'start_onboarding',
122
+ 'suggest_mcp_server',
123
+ 'suggest_mcps',
124
+ 'suggest_stack',
125
+ 'suggest_token_optimizer',
126
+ 'suggest_tooling',
127
+ 'summarize_spec',
128
+ 'sync_ai_configs',
129
+ 'tdd_scaffold',
130
+ 'token_optimizer_status',
131
+ 'tool',
132
+ 'type_safety_gate',
133
+ 'update_registry',
134
+ 'update_status',
135
+ 'update_status_batch',
136
+ 'validate',
137
+ 'validate_api_contract',
138
+ 'validate_criteria_quality',
139
+ 'validate_docs_registry',
140
+ 'verify_spec_compliance',
141
+ 'worker_status',
142
+ ];
143
+ const LEGACY_OR_RUNTIME_TOOL_NAMES = [
144
+ 'multi_agent_review',
145
+ 'orchestrate_runtime',
146
+ 'package_handoff',
147
+ 'recommend_model',
148
+ ];
149
+ const LLM_REQUIRED_TOOL_NAMES = ['create_spec'];
150
+ const HYBRID_TOOL_NAMES = [
151
+ 'challenge_spec',
152
+ 'clarify_requirements',
153
+ 'consult_docs',
154
+ 'define_ui_contract',
155
+ 'design_schema',
156
+ 'estimate',
157
+ 'eval_skill',
158
+ 'generate_adr',
159
+ 'generate_api_client',
160
+ 'generate_checklist',
161
+ 'generate_compliance_report',
162
+ 'generate_compliance_tests',
163
+ 'generate_docs',
164
+ 'generate_docs_site',
165
+ 'generate_execution_plan',
166
+ 'generate_orchestration_script',
167
+ 'generate_rules',
168
+ 'generate_skill',
169
+ 'generate_sub_agent',
170
+ 'generate_tests',
171
+ 'generate_validation_schema',
172
+ 'multi_agent_review',
173
+ 'paradigm_report',
174
+ 'reconcile_spec',
175
+ 'red_team',
176
+ 'reverse_engineer',
177
+ 'rewrite_criteria_ears',
178
+ 'spec_obesity_healer',
179
+ 'ssr_back_migration',
180
+ 'suggest_mcp_server',
181
+ 'suggest_mcps',
182
+ 'suggest_stack',
183
+ 'suggest_token_optimizer',
184
+ 'suggest_tooling',
185
+ 'tdd_scaffold',
186
+ ];
187
+ const NO_LLM_LOCAL_TOOL_NAMES = [
188
+ 'approval_status',
189
+ 'approve_spec',
190
+ 'check_versions',
191
+ 'code_graph_status',
192
+ 'config_health',
193
+ 'configure_approval_policy',
194
+ 'configure_code_graph',
195
+ 'configure_compliance',
196
+ 'configure_compliance_gate',
197
+ 'configure_memory',
198
+ 'configure_roles',
199
+ 'configure_squad',
200
+ 'configure_workers',
201
+ 'context_budget',
202
+ 'detect_agent',
203
+ 'discover_docs_url',
204
+ 'discover_mcps',
205
+ 'discover_registry',
206
+ 'ears_lint',
207
+ 'free_tool',
208
+ 'issue_reviewer_token',
209
+ 'list_spec_lint_rules',
210
+ 'list_spec_prompts',
211
+ 'list_specs',
212
+ 'manage_context',
213
+ 'manage_git',
214
+ 'manage_plugins',
215
+ 'memory_status',
216
+ 'oauth_status',
217
+ 'orchestrate',
218
+ 'orchestrate_runtime',
219
+ 'package_handoff',
220
+ 'pin_version',
221
+ 'pro_tool',
222
+ 'registry_install',
223
+ 'registry_login',
224
+ 'registry_logout',
225
+ 'registry_publish',
226
+ 'registry_search',
227
+ 'registry_whoami',
228
+ 'run_spec_lint',
229
+ 'scan_orphan_spec_refs',
230
+ 'search_all_projects',
231
+ 'set_locale',
232
+ 'set_work_mode',
233
+ 'skill_install',
234
+ 'skill_search',
235
+ 'squad_status',
236
+ 'start_oauth_flow',
237
+ 'summarize_spec',
238
+ 'token_optimizer_status',
239
+ 'tool',
240
+ 'update_registry',
241
+ 'update_status',
242
+ 'update_status_batch',
243
+ 'validate',
244
+ 'worker_status',
245
+ ];
246
+ const EXPLICIT_TOOL_NAMES = new Set([
247
+ ...REGISTERED_TOOL_NAMES,
248
+ ...LEGACY_OR_RUNTIME_TOOL_NAMES,
249
+ ]);
250
+ const LLM_REQUIRED_TOOLS = new Set(LLM_REQUIRED_TOOL_NAMES);
251
+ const HYBRID_TOOLS = new Set(HYBRID_TOOL_NAMES);
252
+ const NO_LLM_LOCAL_TOOLS = new Set(NO_LLM_LOCAL_TOOL_NAMES);
253
+ function executionClassFor(toolName) {
254
+ if (LLM_REQUIRED_TOOLS.has(toolName)) {
255
+ return 'llm-required';
256
+ }
257
+ if (HYBRID_TOOLS.has(toolName)) {
258
+ return 'hybrid';
259
+ }
260
+ return 'local';
261
+ }
262
+ function reasonFor(toolName, executionClass) {
263
+ if (executionClass === 'llm-required') {
264
+ return 'Creates the primary spec artifact and requires language synthesis from user intent.';
265
+ }
266
+ if (executionClass === 'hybrid') {
267
+ return 'Runs local discovery, validation, or scaffolding first; only synthesis/review steps should use LLM tokens.';
268
+ }
269
+ if (NO_LLM_LOCAL_TOOLS.has(toolName)) {
270
+ return 'Uses deterministic local project state and should not call an LLM by default.';
271
+ }
272
+ return 'Processes local project artifacts and returns bounded summaries before any model-facing handoff.';
273
+ }
274
+ function tokenPolicyFor(toolName, executionClass) {
275
+ if (executionClass === 'llm-required') {
276
+ return 'llm-product';
277
+ }
278
+ if (executionClass === 'local' && NO_LLM_LOCAL_TOOLS.has(toolName)) {
279
+ return 'no-llm';
280
+ }
281
+ return 'summarize-first';
282
+ }
283
+ function buildClassification(toolName) {
284
+ const executionClass = executionClassFor(toolName);
285
+ return {
286
+ toolName,
287
+ executionClass,
288
+ tokenPolicy: tokenPolicyFor(toolName, executionClass),
289
+ reason: reasonFor(toolName, executionClass),
290
+ };
291
+ }
292
+ const TOOL_CLASSIFICATIONS = new Map([...EXPLICIT_TOOL_NAMES].map((toolName) => [toolName, buildClassification(toolName)]));
293
+ export function getToolClassification(toolName) {
294
+ return (TOOL_CLASSIFICATIONS.get(toolName) ?? {
295
+ toolName,
296
+ executionClass: 'hybrid',
297
+ tokenPolicy: 'summarize-first',
298
+ reason: 'Unregistered tools default to local preprocessing with bounded summaries.',
299
+ });
300
+ }
301
+ export function hasExplicitToolClassification(toolName) {
302
+ return TOOL_CLASSIFICATIONS.has(toolName);
303
+ }
304
+ export function isLocalFirstTool(toolName) {
305
+ return getToolClassification(toolName).executionClass !== 'llm-required';
306
+ }
307
+ export function requiresLlmByDefault(toolName) {
308
+ return getToolClassification(toolName).executionClass === 'llm-required';
309
+ }
310
+ export function listToolClassifications() {
311
+ return [...TOOL_CLASSIFICATIONS.values()].sort((a, b) => a.toolName.localeCompare(b.toolName));
312
+ }
313
+ //# sourceMappingURL=tool-classification.js.map
@@ -20,7 +20,19 @@ function runCheck(name, command, args, cwd) {
20
20
  const durationMs = Date.now() - start;
21
21
  const passed = result.status === 0 && !result.error;
22
22
  const output = [result.stdout, result.stderr].join('\n').trim().slice(0, 2000);
23
- return { name, passed, output, durationMs };
23
+ const errorCode = result.error && 'code' in result.error
24
+ ? String(result.error.code)
25
+ : undefined;
26
+ const timedOut = errorCode === 'ETIMEDOUT' || result.signal === 'SIGTERM';
27
+ return {
28
+ name,
29
+ passed,
30
+ output,
31
+ durationMs,
32
+ command: [command, ...args].join(' '),
33
+ timedOut,
34
+ errorMessage: result.error?.message,
35
+ };
24
36
  }
25
37
  export function runQaGate(spec, projectPath) {
26
38
  const coverageThreshold = extractCoverageThreshold(spec);
@@ -11,11 +11,23 @@ function normalize(text) {
11
11
  /**
12
12
  * Extract keywords from an out-of-scope item phrase (>3 chars).
13
13
  */
14
- const STOP_WORDS = new Set(['the', 'and', 'for', 'with', 'that', 'this', 'from', 'will']);
14
+ const STOP_WORDS = new Set(['the', 'and', 'for', 'with', 'that', 'this', 'from', 'will', 'every']);
15
+ const GENERIC_SCOPE_TOKENS = new Set([
16
+ 'code',
17
+ 'file',
18
+ 'files',
19
+ 'module',
20
+ 'modules',
21
+ 'mcp',
22
+ 'response',
23
+ 'responses',
24
+ 'tool',
25
+ 'tools',
26
+ ]);
15
27
  function keywords(phrase) {
16
28
  return normalize(phrase)
17
29
  .split(' ')
18
- .filter((w) => w.length > 3 && !STOP_WORDS.has(w));
30
+ .filter((w) => w.length > 2 && !STOP_WORDS.has(w) && !GENERIC_SCOPE_TOKENS.has(w));
19
31
  }
20
32
  /**
21
33
  * Determine whether a file path matches an out-of-scope item.
@@ -9,7 +9,7 @@ export async function writeImplementationReviewReport(input) {
9
9
  specCompliance,
10
10
  minimalityReport: input.minimalityReport,
11
11
  });
12
- const mergedGates = await preserveUnobservedBlockingGates(input, gates);
12
+ const mergedGates = await preserveUnobservedGates(input, gates);
13
13
  const passed = mergedGates.every((gate) => gate.passed);
14
14
  const reviewer = {
15
15
  kind: 'implementation-review-agent',
@@ -56,7 +56,7 @@ export async function writeImplementationReviewReport(input) {
56
56
  };
57
57
  }
58
58
  }
59
- async function preserveUnobservedBlockingGates(input, nextGates) {
59
+ async function preserveUnobservedGates(input, nextGates) {
60
60
  const currentGateNames = new Set(nextGates.map((gate) => gate.name));
61
61
  const explicitlyObserved = new Set([
62
62
  ...(input.lintPassed !== undefined ? ['lint'] : []),
@@ -71,8 +71,7 @@ async function preserveUnobservedBlockingGates(input, nextGates) {
71
71
  if (!existing?.ok) {
72
72
  return nextGates;
73
73
  }
74
- const preserved = existing.payload.gates.filter((gate) => !gate.passed &&
75
- currentGateNames.has(gate.name) &&
74
+ const preserved = existing.payload.gates.filter((gate) => currentGateNames.has(gate.name) &&
76
75
  !explicitlyObserved.has(gate.name) &&
77
76
  (gate.name === 'lint' ||
78
77
  gate.name === 'convention-regression' ||
@@ -139,8 +139,8 @@ export async function handleAudit(args) {
139
139
  findingsCount: String(result.findings.length),
140
140
  }),
141
141
  },
142
- { type: 'text', text: JSON.stringify(output, null, 2) },
143
142
  ],
143
+ structuredContent: output,
144
144
  };
145
145
  }
146
146
  function scoreToGrade(score) {
@@ -32,6 +32,7 @@ const ALL_FOCUS_AREAS = [
32
32
  'security',
33
33
  'data-consistency',
34
34
  ];
35
+ const ACTIONABLE_RELEVANCE_MIN = 15;
35
36
  /**
36
37
  * SPEC-615 AC3: detect contradictions between spec criteria and prior decisions.
37
38
  * Mutates failureScenarios in place. Best-effort — never throws.
@@ -242,8 +243,16 @@ export async function handleChallengeSpec(args, server) {
242
243
  const match = prioritized.find((p) => p.scenario === s.scenario);
243
244
  return match !== undefined ? { ...s, relevanceScore: match.relevanceScore } : s;
244
245
  });
246
+ const prioritizedScenarios = new Set(prioritized.map((scenario) => scenario.scenario));
247
+ const actionableFailureScenarios = failureScenariosScored.filter((scenario) => prioritizedScenarios.has(scenario.scenario) ||
248
+ scenario.impact === 'critical' ||
249
+ scenario.impact === 'high' ||
250
+ isDomainRelevantScenario(scenario, specContent, knowledge) ||
251
+ (scenario.relevanceScore ?? computeScenarioRelevanceFallback(scenario)) >=
252
+ ACTIONABLE_RELEVANCE_MIN);
253
+ const suppressedScenarioCount = failureScenariosScored.length - actionableFailureScenarios.length;
245
254
  const analysis = {
246
- failureScenarios: failureScenariosScored,
255
+ failureScenarios: actionableFailureScenarios,
247
256
  concurrencyAnalysis,
248
257
  scalabilityAssessment,
249
258
  overallRisk,
@@ -262,6 +271,8 @@ export async function handleChallengeSpec(args, server) {
262
271
  },
263
272
  summary: {
264
273
  totalScenarios: failureScenarios.length,
274
+ actionableScenarios: actionableFailureScenarios.length,
275
+ suppressedLowRelevanceScenarios: suppressedScenarioCount,
265
276
  shownByDefault: 3,
266
277
  mustAddressBeforeCoding: prioritizedSummary,
267
278
  criticalImpact: failureScenarios.filter((s) => s.impact === 'critical').length,
@@ -354,9 +365,48 @@ export async function handleChallengeSpec(args, server) {
354
365
  scenarioCount: String(failureScenarios.length),
355
366
  }),
356
367
  },
357
- { type: 'text', text: JSON.stringify(analysisPayload, null, 2) },
368
+ {
369
+ type: 'text',
370
+ text: `Actionable scenarios: ${String(actionableFailureScenarios.length)}` +
371
+ ` | Suppressed low-relevance: ${String(suppressedScenarioCount)}` +
372
+ ` | Overall risk: ${overallRisk}`,
373
+ },
358
374
  { type: 'text', text: humanSummary },
359
375
  ],
376
+ structuredContent: analysisPayload,
360
377
  };
361
378
  }
379
+ function computeScenarioRelevanceFallback(scenario) {
380
+ if (scenario.impact === 'critical') {
381
+ return 100;
382
+ }
383
+ if (scenario.impact === 'high') {
384
+ return 70;
385
+ }
386
+ if (scenario.probability === 'high') {
387
+ return 35;
388
+ }
389
+ return 0;
390
+ }
391
+ function isDomainRelevantScenario(scenario, specContent, knowledge) {
392
+ const haystack = `${scenario.scenario} ${scenario.currentHandling} ${scenario.requiredHandling}`
393
+ .toLowerCase()
394
+ .trim();
395
+ const specText = specContent.toLowerCase();
396
+ const framework = knowledge.framework?.toLowerCase() ?? '';
397
+ const isGameProject = knowledge.projectCategory === 'game' ||
398
+ ['unity', 'godot', 'unreal', 'bevy', 'phaser', 'pygame'].includes(framework);
399
+ if (isGameProject && /\b(game|physics|cheat|balance|save|hud|player)\b/.test(haystack)) {
400
+ return true;
401
+ }
402
+ if (/\b(database|query|index|pagination|transaction|cache)\b/.test(haystack) &&
403
+ /\b(database|query|index|pagination|transaction|cache|sql|postgres|sqlite)\b/.test(specText)) {
404
+ return true;
405
+ }
406
+ if (/\b(api|auth|jwt|session|csrf|injection|tenant|bola)\b/.test(haystack) &&
407
+ /\b(api|auth|jwt|session|csrf|injection|tenant|bola)\b/.test(specText)) {
408
+ return true;
409
+ }
410
+ return false;
411
+ }
362
412
  //# sourceMappingURL=challenge-spec.js.map
@@ -6,6 +6,9 @@ import { buildCheckReadinessSummary } from '../engine/human-summary.js';
6
6
  import { validateSpecFormat } from '../core/spec-validator.js';
7
7
  import { resolveProjectId } from './resolve-project-id.js';
8
8
  // ── Formatting helpers ───────────────────────────────────────────────────────
9
+ const MAX_VISIBLE_BLOCKERS = 8;
10
+ const MAX_VISIBLE_WARNINGS = 8;
11
+ const MAX_VISIBLE_RECOMMENDATIONS = 5;
9
12
  function formatScore(score) {
10
13
  if (score >= 90) {
11
14
  return `${score}/100 (Excellent)`;
@@ -50,34 +53,38 @@ function formatReport(report) {
50
53
  if (report.issues.blockers.length > 0) {
51
54
  lines.push('## Blockers');
52
55
  lines.push('');
53
- for (const blocker of report.issues.blockers) {
56
+ for (const blocker of report.issues.blockers.slice(0, MAX_VISIBLE_BLOCKERS)) {
54
57
  lines.push(`- BLOCKER: ${blocker}`);
55
58
  }
59
+ if (report.issues.blockers.length > MAX_VISIBLE_BLOCKERS) {
60
+ lines.push(`- ...and ${String(report.issues.blockers.length - MAX_VISIBLE_BLOCKERS)} more`);
61
+ }
56
62
  lines.push('');
57
63
  }
58
64
  // Warnings
59
65
  if (report.issues.warnings.length > 0) {
60
66
  lines.push('## Warnings');
61
67
  lines.push('');
62
- for (const warning of report.issues.warnings) {
68
+ for (const warning of report.issues.warnings.slice(0, MAX_VISIBLE_WARNINGS)) {
63
69
  lines.push(`- WARNING: ${warning}`);
64
70
  }
71
+ if (report.issues.warnings.length > MAX_VISIBLE_WARNINGS) {
72
+ lines.push(`- ...and ${String(report.issues.warnings.length - MAX_VISIBLE_WARNINGS)} more`);
73
+ }
65
74
  lines.push('');
66
75
  }
67
76
  // Recommendations
68
77
  if (report.recommendations.length > 0) {
69
78
  lines.push('## Recommendations');
70
79
  lines.push('');
71
- for (const rec of report.recommendations) {
80
+ for (const rec of report.recommendations.slice(0, MAX_VISIBLE_RECOMMENDATIONS)) {
72
81
  lines.push(`- ${rec}`);
73
82
  }
83
+ if (report.recommendations.length > MAX_VISIBLE_RECOMMENDATIONS) {
84
+ lines.push(`- ...and ${String(report.recommendations.length - MAX_VISIBLE_RECOMMENDATIONS)} more`);
85
+ }
74
86
  lines.push('');
75
87
  }
76
- // SPEC-042: Config health hint
77
- lines.push('## Config Health');
78
- lines.push('');
79
- lines.push('- Run `check_config_health` to detect silent misconfigurations (tsconfig coverage, CI script references, build artifact gaps) that block DoR.');
80
- lines.push('');
81
88
  return lines.join('\n');
82
89
  }
83
90
  // ── Handler ──────────────────────────────────────────────────────────────────