@polymorphism-tech/morph-spec 8.2.1 → 8.3.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 (42) hide show
  1. package/README.md +70 -99
  2. package/bin/morph-spec.js +12 -0
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +76 -135
  5. package/docs/QUICKSTART.md +35 -51
  6. package/framework/CLAUDE.md +1 -0
  7. package/framework/agents.json +3 -1
  8. package/framework/hooks/README.md +3 -0
  9. package/framework/hooks/claude-code/core/telemetry-log.js +102 -0
  10. package/framework/hooks/shared/telemetry-logger.js +75 -0
  11. package/framework/rules/ai-agents-standards.md +1 -0
  12. package/framework/schemas/feature.schema.json +0 -5
  13. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  14. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  15. package/framework/skills/core/morph-apply/SKILL.md +1 -1
  16. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  17. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  18. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  19. package/framework/skills/core/morph-implement/SKILL.md +1 -1
  20. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  21. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  22. package/framework/skills/core/morph-review/SKILL.md +1 -1
  23. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  24. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  25. package/framework/standards/STANDARDS.json +43 -1
  26. package/framework/standards/ai-agents/observability-patterns.md +156 -0
  27. package/framework/standards/ai-agents/production.md +3 -47
  28. package/framework/templates/REGISTRY.json +0 -93
  29. package/package.json +1 -1
  30. package/src/commands/observability/telemetry.js +75 -0
  31. package/src/core/state/state-manager.js +0 -128
  32. package/src/dashboard/app/pages/LiveState.jsx +0 -43
  33. package/src/lib/detectors/design-system-detector.js +0 -43
  34. package/src/lib/generators/index.js +0 -1
  35. package/src/lib/telemetry/telemetry-reader.js +103 -0
  36. package/src/lib/validators/validation-runner.js +9 -73
  37. package/src/utils/hooks-installer.js +27 -3
  38. package/framework/templates/context/CONTEXT-FEATURE.md +0 -269
  39. package/framework/templates/context/CONTEXT.md +0 -181
  40. package/src/lib/agents/metrics-aggregator.js +0 -56
  41. package/src/lib/agents/metrics-calculator.js +0 -125
  42. package/src/lib/generators/metadata-extractor.js +0 -385
@@ -0,0 +1,156 @@
1
+ # Observability Patterns — OpenTelemetry, enrichment, investigation
2
+
3
+ > **Scope:** stacks=["dotnet"]
4
+ > **Layer:** 1 (on-keyword)
5
+ > **Keywords:** observability, telemetry, opentelemetry, logging, tracing, log investigation, troubleshooting, aspire dashboard, span, correlation id
6
+ > **Read by Claude in:** implement (wiring telemetry into an agent) and troubleshoot (investigating a production issue)
7
+
8
+ **Verified against:** MAF 1.0 GA. Last-verified: 2026-07-22.
9
+
10
+ ---
11
+
12
+ ## Por que isso importa
13
+
14
+ Um agente MAF em produção não é uma função — é uma cadeia (LLM call → tool call → sub-agent → RAG/DB) que pode ter qualquer profundidade. Sem telemetria estruturada, um bug em produção ("o agente respondeu errado", "a automação não disparou") é indistinguível de uma caixa-preta: não dá pra saber qual chamada falhou, quanto cada etapa levou, ou qual tool retornou o quê. Este standard cobre as três partes do problema: **como instrumentar** (setup), **o que registrar** (enrichment) e **como investigar** (análise) — nessa ordem, porque setup sem enrichment produz traces vazios, e enrichment sem prática de investigação não ajuda ninguém no momento do incidente.
15
+
16
+ ---
17
+
18
+ ## Setup — OpenTelemetry
19
+
20
+ ```csharp
21
+ using OpenTelemetry;
22
+ using OpenTelemetry.Trace;
23
+ using OpenTelemetry.Metrics;
24
+ using Microsoft.Agents.AI;
25
+
26
+ // Habilitar em agents individuais
27
+ agent.WithOpenTelemetry();
28
+
29
+ // Program.cs — setup completo
30
+ builder.Services.AddOpenTelemetry()
31
+ .WithTracing(tracing =>
32
+ {
33
+ tracing.AddSource(AgentOpenTelemetryConsts.DefaultSourceName); // MAF 1.0 GA source
34
+ tracing.AddSource(builder.Environment.ApplicationName); // seus spans customizados (ver Enrichment)
35
+ tracing.AddAspNetCoreInstrumentation();
36
+ tracing.AddHttpClientInstrumentation();
37
+ tracing.AddOtlpExporter();
38
+ })
39
+ .WithMetrics(metrics =>
40
+ {
41
+ metrics.AddMeter(AgentOpenTelemetryConsts.DefaultSourceName);
42
+ metrics.AddAspNetCoreInstrumentation();
43
+ metrics.AddHttpClientInstrumentation();
44
+ metrics.AddRuntimeInstrumentation();
45
+ });
46
+
47
+ builder.Logging.AddOpenTelemetry(logging =>
48
+ {
49
+ logging.IncludeFormattedMessage = true;
50
+ logging.IncludeScopes = true; // necessário para correlação — ver Enrichment
51
+ logging.AddOtlpExporter();
52
+ });
53
+ ```
54
+
55
+ > **Source name:** sempre `AgentOpenTelemetryConsts.DefaultSourceName` de `Microsoft.Agents.AI`.
56
+ > **NÃO usar** o source string `"Experimental.[pacote-antigo].*"` — pertence à família `Microsoft.Extensions.AI` 9.x, incompatível com MAF 1.0 GA.
57
+ > **Produção:** desabilite dados sensíveis — `EnableSensitiveTelemetryData = false` no agent — para não vazar prompt/response completos no backend de export.
58
+
59
+ ### Aspire Dashboard (dev/local)
60
+
61
+ Visualiza em tempo real: fluxo completo de conversas entre agents, ciclos de tool call (Model → Tool → Model), token usage e latência por chamada, error traces e retry patterns. Para ambientes hospedados (Coolify), exporte via OTLP para o backend de observability do projeto — ver `infrastructure-docker-coolify-deploy` para o setup de Serilog/JSON console log que alimenta o mesmo pipeline de agregação.
62
+
63
+ ---
64
+
65
+ ## Enrichment — o que preencher em spans e logs
66
+
67
+ Setup sem enrichment produz traces tecnicamente corretos mas operacionalmente inúteis — você vê "uma chamada ao LLM aconteceu", não "por que ela demorou 8s" ou "qual feature/task gerou essa chamada". Preencha sempre:
68
+
69
+ ### Correlação entre trace, feature e requisição
70
+
71
+ ```csharp
72
+ using var activity = Activity.Current;
73
+ activity?.SetTag("morph.feature", featureName);
74
+ activity?.SetTag("morph.task_id", taskId);
75
+ activity?.SetTag("morph.correlation_id", correlationId); // mesmo ID em toda a cadeia de uma requisição de usuário
76
+ ```
77
+
78
+ Sem um `correlation_id` propagado manualmente através de chamadas assíncronas/filas (Hangfire, background jobs), você não consegue religar "o usuário reclamou às 14:32" a um trace específico — o `TraceId` do OpenTelemetry sozinho não sobrevive a um boundary de fila.
79
+
80
+ ### Atributos de custo e uso do modelo
81
+
82
+ ```csharp
83
+ activity?.SetTag("gen_ai.request.model", modelAlias); // alias do Model Registry, nunca o nome hardcoded
84
+ activity?.SetTag("gen_ai.usage.prompt_tokens", usage.PromptTokens);
85
+ activity?.SetTag("gen_ai.usage.completion_tokens", usage.CompletionTokens);
86
+ activity?.SetTag("gen_ai.response.finish_reason", finishReason);
87
+ ```
88
+
89
+ Nomeação `gen_ai.*` segue a convenção semântica OpenTelemetry para GenAI — mantém compatibilidade se o backend de observability tiver dashboards prontos para essa convenção.
90
+
91
+ ### Logging estruturado com `ILogger` — sempre com scope
92
+
93
+ ```csharp
94
+ using (_logger.BeginScope(new Dictionary<string, object>
95
+ {
96
+ ["FeatureId"] = featureName,
97
+ ["TaskId"] = taskId,
98
+ ["AgentId"] = agentPersonaId
99
+ }))
100
+ {
101
+ _logger.LogInformation("Tool call {ToolName} started with args {Args}", toolName, args);
102
+ // ... chamada ...
103
+ _logger.LogInformation("Tool call {ToolName} completed in {ElapsedMs}ms", toolName, elapsed);
104
+ }
105
+ ```
106
+
107
+ - **Sempre** parâmetros nomeados no template (`{ToolName}`, não string interpolada) — permite querying estruturado no backend em vez de grep de texto livre.
108
+ - **Nunca** logar o conteúdo completo do prompt/response em `Information` — isso é PII/dado sensível do cliente por padrão. Logar em `Debug` apenas, e só se `EnableSensitiveTelemetryData` estiver habilitado explicitamente para o ambiente.
109
+ - **Nível de log por tipo de evento:** `Information` para início/fim de tool call e decisões do agent; `Warning` para retry/fallback de provider; `Error` apenas para falhas que geram resposta degradada ao usuário (não para toda exceção capturada e tratada).
110
+
111
+ ---
112
+
113
+ ## Investigação e análise — quando algo dá errado
114
+
115
+ Workflow para diagnosticar um incidente relatado ("o agente X respondeu errado/lento/nada às HH:MM"):
116
+
117
+ 1. **Ancore no correlation_id ou janela de tempo.** Se o relato tem um identificador (número de WhatsApp, feature, conversationId), busque por ele nos atributos (`morph.correlation_id`, `gen_ai.*`) — não pelo texto do log. Sem identificador, restrinja pela janela de tempo + `morph.feature`/`AgentId` antes de olhar qualquer trace individual.
118
+ 2. **Suba do trace raiz para os spans filhos.** O span raiz mostra a duração total; os filhos (`Function Calling`, `IChatClient`) mostram onde o tempo/erro realmente está. Não assuma — meça. Um "agente lento" é quase sempre UM tool call específico (chamada de API externa, query sem índice), não o LLM.
119
+ 3. **Correlacione com métricas antes de teorizar.** Confira a tabela "What to Monitor" abaixo pelo período do incidente — um pico de `tool call failure rate` ou `token usage` aponta a causa mais rápido que ler traces individuais um a um.
120
+ 4. **Reproduza com Debug ligado, nunca em produção.** Se a causa não estiver clara nos atributos estruturados, habilite `Debug` (com dados sensíveis, se necessário) num ambiente de staging com o mesmo input — nunca habilite log de prompt/response completo em produção para investigar.
121
+ 5. **Documente a causa raiz em `decisions.md`** se a investigação levar a uma mudança de arquitetura (novo middleware de retry, novo timeout, novo cache) — não só no commit message.
122
+
123
+ ### What to Monitor
124
+
125
+ | Metric | Purpose | Alert Threshold |
126
+ |--------|---------|------------------|
127
+ | Token usage per request | Cost control | > 10K tokens/request |
128
+ | Latency per agent call | Performance | > 5s for simple tasks |
129
+ | Tool call failure rate | Reliability | > 5% failures |
130
+ | Cache hit rate | Cost optimization | < 50% (tune strategy) |
131
+ | Model drift | Quality | Evaluation score drop > 10% |
132
+
133
+ ---
134
+
135
+ ## Checklist (verifiable by morph-eval)
136
+
137
+ - [ ] OpenTelemetry configurado com `AgentOpenTelemetryConsts.DefaultSourceName` (traces + metrics + logging)
138
+ - [ ] `EnableSensitiveTelemetryData = false` fora de dev/staging
139
+ - [ ] Todo tool call relevante tem `correlation_id` propagado manualmente através de boundaries assíncronos (filas, background jobs)
140
+ - [ ] Logging via `ILogger` com `BeginScope` estruturado, nunca string interpolada em templates de log
141
+ - [ ] Nenhum log em `Information`/produção contém prompt/response completo
142
+ - [ ] Alertas configurados para os thresholds da tabela "What to Monitor" relevantes ao projeto
143
+
144
+ ---
145
+
146
+ ## References
147
+
148
+ - `ai-agents-production` — panorama de produção; esta seção era resumida lá, tratamento canônico agora aqui
149
+ - `ai-agents-middleware-patterns` — middleware de telemetria no nível `IChatClient` (`TelemetryMiddleware`)
150
+ - `infrastructure-docker-coolify-deploy` — Serilog/JSON console log para o pipeline de agregação em produção
151
+ - [OpenTelemetry Semantic Conventions for GenAI](https://opentelemetry.io/docs/specs/semconv/gen-ai/)
152
+ - [Agent Observability](https://learn.microsoft.com/agent-framework/user-guide/observability/)
153
+
154
+ ---
155
+
156
+ *MORPH-SPEC by Polymorphism Tech — ai-agents/observability-patterns.md v1.0 (2026-07-22)*
@@ -179,54 +179,9 @@ public async Task<WeatherResponse> GetWeatherAsync(
179
179
 
180
180
  ## Observability / OpenTelemetry
181
181
 
182
- ```csharp
183
- using OpenTelemetry;
184
- using OpenTelemetry.Trace;
185
- using OpenTelemetry.Metrics;
186
- using Microsoft.Agents.AI;
187
-
188
- // Enable on individual agents
189
- agent.WithOpenTelemetry();
190
-
191
- // Program.cs — full setup
192
- builder.Services.AddOpenTelemetry()
193
- .WithTracing(tracing =>
194
- {
195
- tracing.AddSource(AgentOpenTelemetryConsts.DefaultSourceName); // MAF 1.0 GA source
196
- tracing.AddSource(builder.Environment.ApplicationName);
197
- tracing.AddAspNetCoreInstrumentation();
198
- tracing.AddHttpClientInstrumentation();
199
- tracing.AddOtlpExporter();
200
- })
201
- .WithMetrics(metrics =>
202
- {
203
- metrics.AddMeter(AgentOpenTelemetryConsts.DefaultSourceName); // MAF 1.0 GA meter
204
- metrics.AddAspNetCoreInstrumentation();
205
- metrics.AddHttpClientInstrumentation();
206
- metrics.AddRuntimeInstrumentation();
207
- });
208
- ```
209
-
210
- > **Source name:** sempre `AgentOpenTelemetryConsts.DefaultSourceName` de `Microsoft.Agents.AI`.
211
- > **NÃO usar** o source string `"Experimental.[pacote-antigo].*"` — pertence à família `Microsoft.Extensions.AI 9.x`, incompatível com MAF 1.0 GA.
212
-
213
- ### Aspire Dashboard
214
-
215
- Visualiza traces de agents incluindo:
216
- - Fluxo completo de conversas entre agents
217
- - Tool call cycles (Model → Tool → Model)
218
- - Token usage e latência por chamada
219
- - Error traces e retry patterns
220
-
221
- ### What to Monitor
182
+ Setup, enrichment (span attributes, correlation IDs, structured logging) e o workflow de investigação/análise de logs em produção têm **tratamento canônico** em **`ai-agents-observability-patterns`**. Não duplicado aqui.
222
183
 
223
- | Metric | Purpose | Alert Threshold |
224
- |--------|---------|----------------|
225
- | Token usage per request | Cost control | > 10K tokens/request |
226
- | Latency per agent call | Performance | > 5s for simple tasks |
227
- | Tool call failure rate | Reliability | > 5% failures |
228
- | Cache hit rate | Cost optimization | < 50% (tune strategy) |
229
- | Model drift | Quality | Evaluation score drop > 10% |
184
+ > Em produção: source name sempre `AgentOpenTelemetryConsts.DefaultSourceName`; `EnableSensitiveTelemetryData = false` fora de dev/staging. Ver `ai-agents-observability-patterns` para o setup completo, o que preencher em cada span/log, e o workflow de investigação quando um incidente for reportado.
230
185
 
231
186
  ---
232
187
 
@@ -262,6 +217,7 @@ Visualiza traces de agents incluindo:
262
217
  - `ai-agents-sweet-spot` — decisão de arquitetura antes de middleware
263
218
  - `ai-agents-providers-model-registry` — Model Registry para `_modelRegistry.GetChatClient(...)`
264
219
  - `ai-agents-middleware-patterns` — **tratamento canônico** de middleware (código dos 3 níveis, telemetria, resilência, anti-hallucination)
220
+ - `ai-agents-observability-patterns` — **tratamento canônico** de observability (setup OpenTelemetry, enrichment de spans/logs, workflow de investigação)
265
221
  - `ai-agents-mcp-tools` — agente MAF consumindo MCP servers externos
266
222
  - `ai-agents-mcp-server` — projeto expondo capacidades como MCP server
267
223
  - `ai-agents-rag-custom-pgvector` — implementação completa de busca semântica (referida em Caching)
@@ -109,55 +109,6 @@
109
109
  "technology": "handlebars",
110
110
  "validatePlaceholders": true
111
111
  },
112
- {
113
- "id": "context",
114
- "kind": "template",
115
- "name": "Project Context",
116
- "path": "context/CONTEXT.md",
117
- "location": "framework",
118
- "category": "context",
119
- "phase": "proposal",
120
- "required": false,
121
- "description": "Project-level context with tech stack, active features, agents, standards",
122
- "outputName": "CONTEXT.md",
123
- "placeholders": [
124
- "AGENT_COUNT",
125
- "AGENT_TEAMS_DISPLAY_MODE",
126
- "AGENT_TEAMS_THRESHOLDS",
127
- "APP_INSIGHTS",
128
- "AZURE_LOCATION",
129
- "CONTAINER_ENV",
130
- "COOLIFY_APP_URL",
131
- "COOLIFY_SERVER",
132
- "DEVOPS_AREA_PATH",
133
- "DEVOPS_BUILD_PIPELINE",
134
- "DEVOPS_ORG",
135
- "DEVOPS_PROJECT",
136
- "DEVOPS_RELEASE_PIPELINE",
137
- "DEVOPS_WIKI_NAME",
138
- "DOTNET_VERSION",
139
- "EF_VERSION",
140
- "INFERRED_STANDARDS",
141
- "MORPH_VERSION",
142
- "NEON_PROJECT_ID",
143
- "NEON_REGION",
144
- "NEXTJS_VERSION",
145
- "OPENAI_MODEL",
146
- "PROJECT_DESCRIPTION",
147
- "PROJECT_NAME",
148
- "PROJECT_TYPE",
149
- "REPOSITORY_URL",
150
- "SQL_DATABASE",
151
- "SQL_SERVER",
152
- "TIMESTAMP",
153
- "UI_LIBRARY"
154
- ],
155
- "stackSpecific": false,
156
- "engine": "handlebars",
157
- "deprecated": false,
158
- "technology": "handlebars",
159
- "validatePlaceholders": true
160
- },
161
112
  {
162
113
  "id": "architecture",
163
114
  "kind": "template",
@@ -176,50 +127,6 @@
176
127
  "technology": "handlebars",
177
128
  "validatePlaceholders": true
178
129
  },
179
- {
180
- "id": "context-feature",
181
- "kind": "template",
182
- "name": "Feature Context",
183
- "path": "context/CONTEXT-FEATURE.md",
184
- "location": "framework",
185
- "category": "context",
186
- "phase": "proposal",
187
- "required": false,
188
- "description": "Feature-level context with progress, agents, tasks, decisions, validations",
189
- "outputName": "CONTEXT-FEATURE.md",
190
- "placeholders": [
191
- "ACTIVE_AGENTS_COUNT",
192
- "AGENT_TEAMS_DECISION",
193
- "COMPLEXITY",
194
- "CONTRACTS_PATH",
195
- "CURRENT_TASK",
196
- "DECISIONS_PATH",
197
- "DESCRIPTION",
198
- "FEATURE_ID",
199
- "FEATURE_NAME",
200
- "MORPH_VERSION",
201
- "PHASE",
202
- "PHASE_DESCRIPTION",
203
- "PROPOSAL_PATH",
204
- "RECAP_PATH",
205
- "SPEC_PATH",
206
- "STATUS",
207
- "TASKS_BLOCKED",
208
- "TASKS_COMPLETED",
209
- "TASKS_IN_PROGRESS",
210
- "TASKS_PATH",
211
- "TASKS_PROGRESS",
212
- "TASKS_TOTAL",
213
- "TEAM_LEAD",
214
- "TIMESTAMP",
215
- "VALIDATION_TIMESTAMP"
216
- ],
217
- "stackSpecific": false,
218
- "engine": "handlebars",
219
- "deprecated": false,
220
- "technology": "handlebars",
221
- "validatePlaceholders": true
222
- },
223
130
  {
224
131
  "id": "spec-examples",
225
132
  "kind": "template",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "8.2.1",
3
+ "version": "8.3.0",
4
4
  "description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `morph-spec telemetry [--json] [--feature <name>] [--limit <n>]`
3
+ *
4
+ * The reader surface over the harness event stream (.morph/logs/events.jsonl,
5
+ * written by the telemetry-log hook). Reconstructs the whole execution path of
6
+ * an NLH session: top skills, personas dispatched, standards (knowledge base)
7
+ * read, the tool mix, and the recent prompts (user + sub-agent).
8
+ *
9
+ * Read-only, always exits 0 (fail-open — an empty stream is an answer).
10
+ */
11
+
12
+ import chalk from 'chalk';
13
+ import { loadTelemetry, aggregateTelemetry } from '../../lib/telemetry/telemetry-reader.js';
14
+
15
+ // The knowledge-base count reflects EXPLICIT reads only. The core standards
16
+ // load via @-refs in rules (Layer 0/1 auto-load) — those are injected by
17
+ // Claude Code's rules system, not via the Read tool, so no hook ever sees them.
18
+ // A low count here means "few on-demand reads", NOT "standards barely used".
19
+ const STANDARDS_CAVEAT =
20
+ 'Só reads explícitos (via Read) são observáveis; standards auto-carregados por @-ref nas rules (Layer 0/1) não passam por tool call e ficam invisíveis aqui.';
21
+
22
+ function section(title, rows, fmt) {
23
+ if (!rows.length) return;
24
+ console.log('\n' + chalk.white.bold(title));
25
+ for (const r of rows) console.log(' ' + fmt(r));
26
+ }
27
+
28
+ /**
29
+ * @param {{ json?: boolean, feature?: string, limit?: number, cwd?: string }} [options]
30
+ */
31
+ export function telemetryCommand(options = {}) {
32
+ const events = loadTelemetry(options.cwd || process.cwd());
33
+ const summary = aggregateTelemetry(events, { feature: options.feature });
34
+
35
+ if (options.json) {
36
+ console.log(JSON.stringify({ ...summary, notes: { standards: STANDARDS_CAVEAT } }));
37
+ process.exit(0);
38
+ }
39
+
40
+ if (summary.total === 0) {
41
+ const scope = options.feature ? ` para a feature "${options.feature}"` : '';
42
+ console.log(chalk.gray(`Telemetria vazia${scope} — nenhum evento capturado ainda em .morph/logs/events.jsonl.`));
43
+ process.exit(0);
44
+ }
45
+
46
+ const scope = options.feature ? chalk.cyan(` · feature ${options.feature}`) : '';
47
+ console.log(chalk.white.bold(`Telemetria do harness — ${summary.total} eventos${scope}`));
48
+ if (summary.features.length) {
49
+ console.log(chalk.gray(`Features: ${summary.features.join(', ')}`));
50
+ }
51
+
52
+ section('Skills', summary.skills, s => `${chalk.green(s.name.padEnd(24))} ${s.count}`);
53
+ section('Personas despachadas', summary.personas, p => `${chalk.magenta(p.name.padEnd(24))} ${p.count}`);
54
+ section('Base de conhecimento (standards lidos explicitamente)', summary.standards,
55
+ s => `${chalk.yellow(String(s.count).padStart(3))}× ${s.path}`);
56
+ if (summary.standards.length || summary.tools.some(t => t.name === 'Read')) {
57
+ console.log(chalk.gray(` ⚠ ${STANDARDS_CAVEAT}`));
58
+ }
59
+ section('Mix de tools', summary.tools, t => `${t.name.padEnd(16)} ${t.count}`);
60
+
61
+ const limit = Number.isInteger(options.limit) && options.limit > 0 ? options.limit : 10;
62
+ const prompts = summary.prompts.slice(-limit);
63
+ if (prompts.length) {
64
+ console.log('\n' + chalk.white.bold(`Prompts (últimos ${prompts.length})`));
65
+ for (const p of prompts) {
66
+ const tag = p.source === 'user' ? chalk.blue('user ') : chalk.magenta('agent');
67
+ const where = p.feature ? chalk.gray(` [${p.feature}/${p.phase || '-'}]`) : '';
68
+ const oneLine = String(p.text || '').replace(/\s+/g, ' ').trim();
69
+ const preview = oneLine.length > 120 ? oneLine.slice(0, 117) + '...' : oneLine;
70
+ console.log(` ${tag}${where} ${preview}`);
71
+ }
72
+ }
73
+
74
+ process.exit(0);
75
+ }
@@ -472,7 +472,6 @@ async function ensureFeature(featureName) {
472
472
  status: "draft",
473
473
  createdAt: new Date().toISOString(),
474
474
  updatedAt: new Date().toISOString(),
475
- activeAgents: [],
476
475
  approvalGates: {
477
476
  proposal: { approved: false, timestamp: null, approvedBy: null },
478
477
  uiux: { approved: false, timestamp: null, approvedBy: null },
@@ -486,7 +485,6 @@ async function ensureFeature(featureName) {
486
485
  pending: 0
487
486
  },
488
487
  checkpoints: [],
489
- dispatchLog: [],
490
488
  trustConfig: {},
491
489
  contextBundles: [],
492
490
  fileChanges: []
@@ -533,7 +531,6 @@ export async function createFeature(featureName, options = {}) {
533
531
  workType: workType || 'feature',
534
532
  createdAt: now,
535
533
  updatedAt: now,
536
- activeAgents: [],
537
534
  approvalGates: {
538
535
  proposal: { approved: false, timestamp: null, approvedBy: null },
539
536
  uiux: { approved: false, timestamp: null, approvedBy: null },
@@ -549,7 +546,6 @@ export async function createFeature(featureName, options = {}) {
549
546
  pending: 0
550
547
  },
551
548
  checkpoints: [],
552
- dispatchLog: [],
553
549
  trustConfig: {},
554
550
  contextBundles: [],
555
551
  fileChanges: []
@@ -649,73 +645,6 @@ export async function addCheckpoint(featureName, note) {
649
645
  return checkpoint;
650
646
  }
651
647
 
652
- /**
653
- * Add agent to feature
654
- * @param {string} featureName - Feature name
655
- * @param {string} agentId - Agent ID
656
- * @returns {boolean} True if added, false if already exists
657
- */
658
- export async function addAgent(featureName, agentId) {
659
- await ensureFeature(featureName);
660
- const state = loadState(); // Load AFTER ensuring feature exists
661
-
662
- if (!state.features[featureName].activeAgents.includes(agentId)) {
663
- state.features[featureName].activeAgents.push(agentId);
664
- state.features[featureName].updatedAt = new Date().toISOString();
665
- saveState(state);
666
- return true;
667
- }
668
-
669
- return false;
670
- }
671
-
672
- /**
673
- * Remove agent from feature
674
- * @param {string} featureName - Feature name
675
- * @param {string} agentId - Agent ID
676
- * @returns {boolean} True if removed, false if not found
677
- */
678
- export function removeAgent(featureName, agentId) {
679
- const state = loadState();
680
-
681
- if (!state.features[featureName]) {
682
- throw new Error(`Feature '${featureName}' not found.`);
683
- }
684
-
685
- const index = state.features[featureName].activeAgents.indexOf(agentId);
686
- if (index > -1) {
687
- state.features[featureName].activeAgents.splice(index, 1);
688
- state.features[featureName].updatedAt = new Date().toISOString();
689
- saveState(state);
690
- return true;
691
- }
692
-
693
- return false;
694
- }
695
-
696
- /**
697
- * Log a dispatch event to state.json.
698
- * Appends entry to feature.dispatchLog, capped at 50 entries (oldest trimmed).
699
- *
700
- * @param {string} featureName - Feature name
701
- * @param {{ timestamp: string, phase: string, mode: string, agents: Array<{ id: string, subagentType: string, group: string, tasks: string[] }> }} entry - Dispatch event to log
702
- */
703
- export function logDispatch(featureName, entry) {
704
- const state = loadState();
705
- const feature = state.features?.[featureName];
706
- if (!feature) return;
707
-
708
- if (!Array.isArray(feature.dispatchLog)) feature.dispatchLog = [];
709
- feature.dispatchLog.push(entry);
710
-
711
- // Cap at 50 entries
712
- if (feature.dispatchLog.length > 50) {
713
- feature.dispatchLog = feature.dispatchLog.slice(-50);
714
- }
715
-
716
- saveState(state);
717
- }
718
-
719
648
  /**
720
649
  * Normalize output type from kebab-case to camelCase (BUG-002 fix - Enhanced)
721
650
  * Supports both kebab-case and camelCase input formats for all output types
@@ -1103,60 +1032,3 @@ export function getApprovalHistory(featureName) {
1103
1032
  // Sort by timestamp
1104
1033
  return history.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
1105
1034
  }
1106
-
1107
- /**
1108
- * Find which agent was assigned a specific task in the latest dispatch.
1109
- * Searches only the last dispatchLog entry.
1110
- *
1111
- * @param {string} featureName
1112
- * @param {string} taskId
1113
- * @returns {string|null} Agent ID or null if not found
1114
- */
1115
- export function resolveAgentForTask(featureName, taskId) {
1116
- const state = loadState();
1117
- const feature = state.features?.[featureName];
1118
- if (!feature) return null;
1119
-
1120
- const log = feature.dispatchLog;
1121
- if (!Array.isArray(log) || log.length === 0) return null;
1122
-
1123
- const latest = log[log.length - 1];
1124
- for (const agent of latest.agents || []) {
1125
- if (Array.isArray(agent.tasks) && agent.tasks.includes(taskId)) {
1126
- return agent.id;
1127
- }
1128
- }
1129
- return null;
1130
- }
1131
-
1132
- /**
1133
- * Update timing fields on a specific agent in the latest dispatch entry.
1134
- * Fail-open: returns silently if feature/dispatch/agent not found.
1135
- *
1136
- * @param {string} featureName
1137
- * @param {string} agentId - Agent ID to update
1138
- * @param {Object} updates - Fields to merge (startedAt, completedAt, tasksCompleted)
1139
- */
1140
- export function updateDispatchAgentTiming(featureName, agentId, updates) {
1141
- const state = loadState();
1142
- const feature = state.features?.[featureName];
1143
- if (!feature) return;
1144
-
1145
- const log = feature.dispatchLog;
1146
- if (!Array.isArray(log) || log.length === 0) return;
1147
-
1148
- const latest = log[log.length - 1];
1149
- const agent = (latest.agents || []).find(a => a.id === agentId);
1150
- if (!agent) return;
1151
-
1152
- Object.assign(agent, updates);
1153
-
1154
- // If all agents now have completedAt, set entry.completedAt to the latest
1155
- const allComplete = (latest.agents || []).every(a => a.completedAt);
1156
- if (allComplete && latest.agents.length > 0) {
1157
- const timestamps = latest.agents.map(a => new Date(a.completedAt).getTime());
1158
- latest.completedAt = new Date(Math.max(...timestamps)).toISOString();
1159
- }
1160
-
1161
- saveState(state);
1162
- }
@@ -98,33 +98,6 @@ function TaskList({ taskList }) {
98
98
  );
99
99
  }
100
100
 
101
- function DispatchLog({ dispatchLog }) {
102
- if (!dispatchLog?.length) return null;
103
-
104
- return (
105
- <div className="mt-3">
106
- <p className="text-gray-500 text-[10px] uppercase tracking-wide mb-1.5">Dispatch Log</p>
107
- <div className="space-y-1 border-l-2 border-gray-700 pl-3">
108
- {dispatchLog.map((entry, i) => (
109
- <div key={i} className="relative">
110
- <span className="absolute -left-4 top-1 w-2 h-2 rounded-full bg-gray-600 border border-gray-500" />
111
- <div className="text-[10px] text-gray-500">
112
- {entry.timestamp && (
113
- <span className="font-mono mr-2">
114
- {new Date(entry.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
115
- </span>
116
- )}
117
- <span className="text-blue-400 font-mono">{entry.agent || entry.agentId}</span>
118
- {entry.task && <span className="text-gray-400 ml-1">→ {entry.task}</span>}
119
- {entry.action && <span className="text-gray-400 ml-1">{entry.action}</span>}
120
- </div>
121
- </div>
122
- ))}
123
- </div>
124
- </div>
125
- );
126
- }
127
-
128
101
  function FeatureCard({ featureId, feature }) {
129
102
  const status = feature.status || 'draft';
130
103
  const phase = feature.phase || feature.currentPhase || PHASE_ORDER[0];
@@ -134,7 +107,6 @@ function FeatureCard({ featureId, feature }) {
134
107
  : typeof feature.taskList === 'object' && feature.taskList !== null
135
108
  ? Object.values(feature.taskList)
136
109
  : [];
137
- const dispatchLog = feature.dispatchLog || [];
138
110
 
139
111
  return (
140
112
  <div className="bg-gray-900 border border-gray-700 rounded-xl p-4 space-y-3">
@@ -169,21 +141,6 @@ function FeatureCard({ featureId, feature }) {
169
141
  <TaskList taskList={taskList} />
170
142
  </div>
171
143
  )}
172
-
173
- {/* Dispatch log */}
174
- <DispatchLog dispatchLog={dispatchLog} />
175
-
176
- {/* Active agents */}
177
- {feature.activeAgents?.length > 0 && (
178
- <div>
179
- <p className="text-gray-500 text-[10px] uppercase tracking-wide mb-1.5">Active Agents</p>
180
- <div className="flex flex-wrap gap-1">
181
- {feature.activeAgents.map((a) => (
182
- <span key={a} className="px-2 py-0.5 bg-gray-800 border border-gray-700 rounded text-[10px] font-mono text-gray-300">{a}</span>
183
- ))}
184
- </div>
185
- </div>
186
- )}
187
144
  </div>
188
145
  );
189
146
  }