@planu/cli 4.10.6 → 4.10.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## [4.10.8] - 2026-07-07
2
+
3
+ ### Refactoring
4
+ - refactor: merge SPEC-1113 native performance benchmark
5
+ - refactor: benchmark native mcp performance
6
+
7
+ ### Chores
8
+ - chore: exclude generated performance report
9
+
10
+
11
+ ## [4.10.7] - 2026-07-07
12
+
13
+ ### Bug Fixes
14
+ - fix(SPEC-1114): reduce MCP token waste
15
+
16
+ ### Chores
17
+ - chore(deps): update patch/minor dependencies
18
+ - chore(planu): clear released pending specs
19
+
20
+
1
21
  ## [4.10.6] - 2026-07-07
2
22
 
3
23
  ### Bug Fixes
@@ -4199,4 +4219,4 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · Versioning:
4199
4219
  - Mermaid diagram generation (architecture, sequence, state machine, ER, data flow)
4200
4220
  - Multi-language i18n (EN/ES/PT) for generated specs
4201
4221
  - Clean Architecture (hexagonal) — engine, tools, storage, types layers
4202
- - 10,857 tests with ≥95% coverage
4222
+ - 10,857 tests with ≥95% coverage
@@ -1,5 +1,6 @@
1
1
  import type { ToolClassification } from '../../types/tool-classification.js';
2
2
  export declare function getToolClassification(toolName: string): ToolClassification;
3
+ export declare function hasExplicitToolClassification(toolName: string): boolean;
3
4
  export declare function isLocalFirstTool(toolName: string): boolean;
4
5
  export declare function requiresLlmByDefault(toolName: string): boolean;
5
6
  export declare function listToolClassifications(): ToolClassification[];
@@ -1,104 +1,295 @@
1
- const TOOL_CLASSIFICATIONS = new Map([
2
- [
3
- 'create_spec',
4
- {
5
- toolName: 'create_spec',
6
- executionClass: 'llm-required',
7
- tokenPolicy: 'llm-product',
8
- reason: 'Generates the primary spec artifact and needs language synthesis.',
9
- },
10
- ],
11
- [
12
- 'validate',
13
- {
14
- toolName: 'validate',
15
- executionClass: 'local',
16
- tokenPolicy: 'summarize-first',
17
- reason: 'Compares repository state and spec artifacts with deterministic validators.',
18
- },
19
- ],
20
- [
21
- 'audit',
22
- {
23
- toolName: 'audit',
24
- executionClass: 'local',
25
- tokenPolicy: 'summarize-first',
26
- reason: 'Runs deterministic checks and returns issue summaries.',
27
- },
28
- ],
29
- [
30
- 'package_handoff',
31
- {
32
- toolName: 'package_handoff',
33
- executionClass: 'local',
34
- tokenPolicy: 'summarize-first',
35
- reason: 'Packages existing spec and project context into an artifact reference.',
36
- },
37
- ],
38
- [
39
- 'list_specs',
40
- {
41
- toolName: 'list_specs',
42
- executionClass: 'local',
43
- tokenPolicy: 'summarize-first',
44
- reason: 'Lists indexed spec metadata and must stay read-only.',
45
- },
46
- ],
47
- [
48
- 'summarize_spec',
49
- {
50
- toolName: 'summarize_spec',
51
- executionClass: 'local',
52
- tokenPolicy: 'summarize-first',
53
- reason: 'Extracts bounded decision fields from an existing spec.',
54
- },
55
- ],
56
- [
57
- 'generate_tests',
58
- {
59
- toolName: 'generate_tests',
60
- executionClass: 'hybrid',
61
- tokenPolicy: 'summarize-first',
62
- reason: 'Can generate deterministic scaffolds locally; novel test design may need LLM input.',
63
- },
64
- ],
65
- [
66
- 'recommend_model',
67
- {
68
- toolName: 'recommend_model',
69
- executionClass: 'local',
70
- tokenPolicy: 'no-llm',
71
- reason: 'Uses local heuristics and project metadata for routing advice.',
72
- },
73
- ],
74
- [
75
- 'multi_agent_review',
76
- {
77
- toolName: 'multi_agent_review',
78
- executionClass: 'hybrid',
79
- tokenPolicy: 'summarize-first',
80
- reason: 'Local orchestration and evidence collection precede any LLM review synthesis.',
81
- },
82
- ],
83
- [
84
- 'audit_specs_drift',
85
- {
86
- toolName: 'audit_specs_drift',
87
- executionClass: 'local',
88
- tokenPolicy: 'summarize-first',
89
- reason: 'Diffs spec and implementation artifacts locally.',
90
- },
91
- ],
92
- [
93
- 'orchestrate_runtime',
94
- {
95
- toolName: 'orchestrate_runtime',
96
- executionClass: 'local',
97
- tokenPolicy: 'no-llm',
98
- reason: 'Selects runtime behavior from local state and explicit policy.',
99
- },
100
- ],
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,
101
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)]));
102
293
  export function getToolClassification(toolName) {
103
294
  return (TOOL_CLASSIFICATIONS.get(toolName) ?? {
104
295
  toolName,
@@ -107,6 +298,9 @@ export function getToolClassification(toolName) {
107
298
  reason: 'Unregistered tools default to local preprocessing with bounded summaries.',
108
299
  });
109
300
  }
301
+ export function hasExplicitToolClassification(toolName) {
302
+ return TOOL_CLASSIFICATIONS.has(toolName);
303
+ }
110
304
  export function isLocalFirstTool(toolName) {
111
305
  return getToolClassification(toolName).executionClass !== 'llm-required';
112
306
  }
@@ -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 ──────────────────────────────────────────────────────────────────
@@ -3,6 +3,7 @@ import { specStore, knowledgeStore } from '../../storage/index.js';
3
3
  import { ti } from '../../i18n/index.js';
4
4
  import { detectPIIInSpec, detectLegalFramework } from '../../engine/pii-detector.js';
5
5
  import { readFile } from 'node:fs/promises';
6
+ import { formatKeyValue } from '../output-formatter.js';
6
7
  // ---------------------------------------------------------------------------
7
8
  // privacy-notice subcommand
8
9
  // ---------------------------------------------------------------------------
@@ -40,14 +41,21 @@ export async function handlePrivacyNotice(projectId) {
40
41
  },
41
42
  {
42
43
  type: 'text',
43
- text: JSON.stringify({
44
+ text: formatKeyValue({
44
45
  subcommand: 'privacy-notice',
45
46
  specsWithPII: privacyItems.length,
46
47
  totalSpecs: specs.length,
47
- noticeText: notice,
48
- }, null, 2),
48
+ noticeChars: notice.length,
49
+ }),
49
50
  },
50
51
  ],
52
+ structuredContent: {
53
+ subcommand: 'privacy-notice',
54
+ specsWithPII: privacyItems.length,
55
+ totalSpecs: specs.length,
56
+ noticeText: notice,
57
+ items: privacyItems,
58
+ },
51
59
  };
52
60
  }
53
61
  // ---------------------------------------------------------------------------
@@ -105,16 +113,23 @@ export async function handleAudit(projectId) {
105
113
  },
106
114
  {
107
115
  type: 'text',
108
- text: JSON.stringify({
116
+ text: formatKeyValue({
109
117
  subcommand: 'audit',
110
118
  totalSpecs: specs.length,
111
119
  specsWithPII: auditItems.filter((i) => i.hasPII).length,
112
120
  nonCompliantSpecs: nonCompliant,
113
121
  framework,
114
- items: auditItems,
115
- }, null, 2),
122
+ }),
116
123
  },
117
124
  ],
125
+ structuredContent: {
126
+ subcommand: 'audit',
127
+ totalSpecs: specs.length,
128
+ specsWithPII: auditItems.filter((i) => i.hasPII).length,
129
+ nonCompliantSpecs: nonCompliant,
130
+ framework,
131
+ items: auditItems,
132
+ },
118
133
  };
119
134
  }
120
135
  // ---------------------------------------------------------------------------