@polymorphism-tech/morph-spec 8.2.1 → 8.4.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 (55) hide show
  1. package/README.md +73 -102
  2. package/bin/morph-spec.js +36 -1
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +77 -134
  5. package/docs/QUICKSTART.md +47 -57
  6. package/framework/CLAUDE.md +6 -3
  7. package/framework/MORPH.md +2 -2
  8. package/framework/agents.json +3 -1
  9. package/framework/commands/morph-hotfix.md +1 -1
  10. package/framework/hooks/README.md +3 -0
  11. package/framework/hooks/claude-code/core/state-sync.js +41 -9
  12. package/framework/hooks/claude-code/core/telemetry-log.js +102 -0
  13. package/framework/hooks/shared/state-writer.js +14 -3
  14. package/framework/hooks/shared/telemetry-logger.js +75 -0
  15. package/framework/rules/ai-agents-standards.md +1 -0
  16. package/framework/schemas/feature.schema.json +15 -7
  17. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  18. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  19. package/framework/skills/core/morph-apply/SKILL.md +1 -1
  20. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  21. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  22. package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
  23. package/framework/skills/core/morph-implement/SKILL.md +1 -1
  24. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  25. package/framework/skills/core/morph-proposal/SKILL.md +4 -4
  26. package/framework/skills/core/morph-review/SKILL.md +1 -1
  27. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  28. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  29. package/framework/standards/STANDARDS.json +43 -1
  30. package/framework/standards/ai-agents/observability-patterns.md +156 -0
  31. package/framework/standards/ai-agents/production.md +3 -47
  32. package/framework/templates/REGISTRY.json +0 -93
  33. package/package.json +1 -1
  34. package/src/commands/observability/telemetry.js +75 -0
  35. package/src/commands/project/doctor.js +28 -0
  36. package/src/commands/state/advance.js +17 -0
  37. package/src/commands/state/approve.js +13 -0
  38. package/src/commands/state/create.js +21 -1
  39. package/src/commands/state/delete.js +143 -0
  40. package/src/commands/state/list.js +110 -0
  41. package/src/core/state/feature-identity.js +36 -0
  42. package/src/core/state/gate-order.js +51 -0
  43. package/src/core/state/lifecycle-audit.js +79 -0
  44. package/src/core/state/state-manager.js +47 -140
  45. package/src/dashboard/app/pages/LiveState.jsx +0 -43
  46. package/src/lib/detectors/design-system-detector.js +0 -43
  47. package/src/lib/generators/index.js +0 -1
  48. package/src/lib/telemetry/telemetry-reader.js +103 -0
  49. package/src/lib/validators/validation-runner.js +9 -73
  50. package/src/utils/hooks-installer.js +27 -3
  51. package/framework/templates/context/CONTEXT-FEATURE.md +0 -269
  52. package/framework/templates/context/CONTEXT.md +0 -181
  53. package/src/lib/agents/metrics-aggregator.js +0 -56
  54. package/src/lib/agents/metrics-calculator.js +0 -125
  55. package/src/lib/generators/metadata-extractor.js +0 -385
@@ -66,8 +66,9 @@ function registerFeatureIfMissing(state, featureName) {
66
66
  if (!state.features) state.features = {};
67
67
  if (state.features[featureName]) return false;
68
68
 
69
- // Canonical object-form gates (incl. optional uiux) — no boolean drift.
70
- state.features[featureName] = createDefaultFeature();
69
+ // Canonical object-form gates (incl. optional uiux) + stamped identity
70
+ // (name=slug, workType, createdAt) — no anonymous shell, no boolean drift.
71
+ state.features[featureName] = createDefaultFeature(featureName);
71
72
  return true;
72
73
  }
73
74
 
@@ -114,6 +115,15 @@ function gateFromQuestion(question) {
114
115
  return null;
115
116
  }
116
117
 
118
+ // Gate monotonicity — mirror of src/core/state/gate-order.js (hooks are
119
+ // installed standalone and cannot import from src/). Keep in sync:
120
+ // proposal → plan → review; uiux requires proposal.
121
+ const GATE_PREREQ = { proposal: [], uiux: ['proposal'], plan: ['proposal'], review: ['plan'] };
122
+ function unmetGatePrereqs(gate, gates) {
123
+ const isApproved = (g) => gates?.[g]?.approved === true || gates?.[g] === true;
124
+ return (GATE_PREREQ[gate] || []).filter((p) => !isApproved(p));
125
+ }
126
+
117
127
  function handleWriteEdit(input, cwd) {
118
128
  const filePath = input.tool_input?.file_path;
119
129
  const featureName = extractFeatureName(filePath);
@@ -139,23 +149,38 @@ function handleWriteEdit(input, cwd) {
139
149
 
140
150
  function handleAskUserQuestion(input, cwd) {
141
151
  const answers = input.tool_response?.answers ?? input.tool_response?.responses ?? {};
142
- if (typeof answers !== 'object' || answers === null) return false;
152
+ if (typeof answers !== 'object' || answers === null) return null;
143
153
 
144
154
  const state = loadState(cwd);
145
- if (!state) return false;
155
+ if (!state) return null;
146
156
 
147
157
  const featureName = findActiveFeature(state);
148
- if (!featureName) return false;
158
+ if (!featureName) return null;
149
159
 
160
+ const warnings = [];
150
161
  for (const [question, answer] of Object.entries(answers)) {
151
162
  if (!isApprovalAnswer(answer)) continue;
152
163
  const gate = gateFromQuestion(question);
153
164
  if (!gate) continue;
154
165
 
155
- const existing = state.features[featureName].approvalGates?.[gate];
166
+ const gates = state.features[featureName].approvalGates;
167
+ const existing = gates?.[gate];
156
168
  const alreadyApproved = existing === true || existing?.approved === true;
157
169
  if (alreadyApproved) continue;
158
170
 
171
+ // Gate monotonicity: fail-open. The human said "aprovado", so we DO approve
172
+ // (never block a human in the loop), but if a predecessor gate is still
173
+ // pending we surface a non-blocking warning — the CLI `morph-spec approve`
174
+ // hard-refuses the same shape; here we alert so the operator can reconcile.
175
+ const unmet = unmetGatePrereqs(gate, gates);
176
+ if (unmet.length) {
177
+ warnings.push(
178
+ `⚠️ [morph-spec] GATE FORA DE ORDEM — o Gate "${gate}" de "${featureName}" foi aprovado, ` +
179
+ `mas o(s) gate(s) anterior(es) ${unmet.join(', ')} não estão aprovados (ordem: ${unmet.join(' → ')} → ${gate}). ` +
180
+ `Aprove os gates anteriores ou reveja com 'morph-spec doctor'.`
181
+ );
182
+ }
183
+
159
184
  // An "aprovado" answer to AskUserQuestion is a human approving in the loop.
160
185
  // Route through the shared canonical gate writer so the hook and the CLI
161
186
  // (`morph-spec approve`) produce one consistent shape
@@ -163,7 +188,7 @@ function handleAskUserQuestion(input, cwd) {
163
188
  // and a manual approval stays distinguishable from a policy auto-approval.
164
189
  setGateApproved(featureName, gate, { approvedBy: 'manual' }, cwd);
165
190
  }
166
- return true;
191
+ return warnings.length ? warnings.join('\n') : null;
167
192
  }
168
193
 
169
194
  async function main() {
@@ -176,17 +201,24 @@ async function main() {
176
201
  const input = readStdin();
177
202
  const toolName = input.tool_name;
178
203
 
204
+ let warning = null;
179
205
  try {
180
206
  if (toolName === 'Write' || toolName === 'Edit') {
181
207
  handleWriteEdit(input, cwd);
182
208
  } else if (toolName === 'AskUserQuestion') {
183
- handleAskUserQuestion(input, cwd);
209
+ warning = handleAskUserQuestion(input, cwd);
184
210
  }
185
211
  } catch {
186
212
  // fail-open
187
213
  }
188
214
 
189
- exit();
215
+ // A non-blocking order-violation warning is surfaced as injected context
216
+ // (hookSpecificOutput.additionalContext); everything else exits silently.
217
+ if (warning) {
218
+ exit({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: warning } });
219
+ } else {
220
+ exit();
221
+ }
190
222
  }
191
223
 
192
224
  main().catch(() => exit());
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Telemetry Hook: harness execution-path capture
5
+ *
6
+ * Events: PostToolUse (all tools) + UserPromptSubmit
7
+ *
8
+ * morph-spec is a Natural Language Harness: there is no central runtime to
9
+ * instrument — Claude Code executes the skills and sub-agents. The only
10
+ * vantage points are hooks. This one is the generic capture point: every tool
11
+ * call and every user prompt is appended (one JSON line) to
12
+ * .morph/logs/events.jsonl, from which `morph-spec telemetry` reconstructs the
13
+ * whole path — which skills ran, which personas were dispatched (with their
14
+ * prompts), which standards/knowledge-base files were read.
15
+ *
16
+ * Correlation is by an EXPLICIT key (feature + phase) written into each event,
17
+ * not by ambient span context — hooks fire as separate node processes with no
18
+ * shared trace context (the enrichment lesson from the observability standard:
19
+ * propagate a correlation key, a TraceId does not survive the boundary).
20
+ *
21
+ * SILENT: never blocks, never injects context — only appends. Fail-open on any
22
+ * error (exit 0). Only active inside a morph project (stateExists gate).
23
+ */
24
+
25
+ import { readStdin } from '../../shared/stdin-reader.js';
26
+ import { stateExists, getActiveFeature, derivePhaseForFeature } from '../../shared/state-reader.js';
27
+ import { appendTelemetryEvent } from '../../shared/telemetry-logger.js';
28
+ import { pass } from '../../shared/hook-response.js';
29
+
30
+ function normalize(p) {
31
+ return (p || '').replace(/\\/g, '/');
32
+ }
33
+
34
+ try {
35
+ if (!stateExists()) pass();
36
+
37
+ const payload = await readStdin();
38
+ if (!payload) pass();
39
+
40
+ const cwd = process.cwd();
41
+
42
+ // Correlation key: active feature + its phase (explicit, not ambient).
43
+ // NOTE (perf): getActiveFeature → loadState hydrates every .morph/features/*/
44
+ // feature.json, so enrichment is O(active-feature-count) per tool call. That
45
+ // is fine for realistic projects (archive removes finished features, so the
46
+ // dir holds only the few in-flight ones) but would grow on a project that
47
+ // keeps many un-archived features. If that ever bites, resolve the active
48
+ // feature from the thin state.json index alone. Kept accurate-over-cheap for
49
+ // now because phase is not stored in feature.json — it needs derivation.
50
+ let feature = null;
51
+ let phase = null;
52
+ try {
53
+ const active = getActiveFeature(cwd);
54
+ if (active) {
55
+ feature = active.name;
56
+ phase = active.feature?.phase || derivePhaseForFeature(active.name, cwd);
57
+ }
58
+ } catch {
59
+ // enrichment is best-effort — a missing feature never blocks capture
60
+ }
61
+
62
+ let event;
63
+
64
+ // UserPromptSubmit carries the user's prompt and no tool_name.
65
+ if (payload.hook_event_name === 'UserPromptSubmit' || (!payload.tool_name && typeof payload.prompt === 'string')) {
66
+ event = { event: 'UserPromptSubmit', tool: 'UserPrompt', prompt: payload.prompt || '' };
67
+ } else {
68
+ const tool = payload.tool_name;
69
+ if (!tool) pass();
70
+ const input = payload.tool_input || {};
71
+ event = { event: 'PostToolUse', tool };
72
+
73
+ if (tool === 'Skill') {
74
+ event.skill = input.skill || '';
75
+ } else if (tool === 'Agent' || tool === 'Task') {
76
+ event.persona = input.subagent_type || '';
77
+ if (input.description) event.desc = input.description;
78
+ event.prompt = input.prompt || '';
79
+ } else if (tool === 'Read') {
80
+ event.target = normalize(input.file_path || input.path);
81
+ if (/\/standards\//.test(event.target)) event.kind = 'standard';
82
+ } else if (tool === 'Bash') {
83
+ event.command = input.command || '';
84
+ } else if (tool === 'Write' || tool === 'Edit') {
85
+ event.target = normalize(input.file_path || input.path);
86
+ } else {
87
+ // Every other tool still counts toward "the whole path" — record a
88
+ // compact target so Grep/Glob/WebFetch bursts remain distinguishable.
89
+ const target = input.file_path || input.path || input.pattern || input.query || input.url || '';
90
+ if (target) event.target = normalize(String(target));
91
+ }
92
+ }
93
+
94
+ event.feature = feature;
95
+ event.phase = phase;
96
+
97
+ appendTelemetryEvent(event, cwd);
98
+ pass();
99
+ } catch {
100
+ // Fail-open
101
+ process.exit(0);
102
+ }
@@ -91,7 +91,7 @@ export function persistFeature(featureName, feature, projectPath) {
91
91
  */
92
92
  export function setGateApproved(featureName, gate, meta = {}, projectPath) {
93
93
  try {
94
- const feature = readFeatureFile(featureName, projectPath) || createDefaultFeature();
94
+ const feature = readFeatureFile(featureName, projectPath) || createDefaultFeature(featureName);
95
95
  const now = new Date().toISOString();
96
96
  feature.approvalGates = feature.approvalGates || {};
97
97
  feature.approvalGates[gate] = {
@@ -106,11 +106,22 @@ export function setGateApproved(featureName, gate, meta = {}, projectPath) {
106
106
  }
107
107
  }
108
108
 
109
- /** Default feature shape (canonical object-form gates, incl. optional uiux). */
110
- export function createDefaultFeature() {
109
+ /**
110
+ * Default feature shape (canonical object-form gates, incl. optional uiux).
111
+ * Stamps identity — name (the slug), workType, createdAt — so the implicit
112
+ * birth path (state-sync registering a feature on the first Write) never
113
+ * produces an anonymous {status,updatedAt} shell. `description` stays absent
114
+ * (a hook can't invent it); `list`/`doctor` flag its absence. Mirrors the CLI
115
+ * createFeature's stamping.
116
+ *
117
+ * @param {string} [featureName] - the feature slug, stamped as `name`
118
+ */
119
+ export function createDefaultFeature(featureName) {
111
120
  const now = new Date().toISOString();
112
121
  return {
122
+ ...(featureName ? { name: featureName } : {}),
113
123
  status: 'in_progress',
124
+ workType: 'feature',
114
125
  createdAt: now,
115
126
  updatedAt: now,
116
127
  approvalGates: {
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Telemetry Logger — append-only harness event stream
3
+ *
4
+ * Writes one JSON line per event to .morph/logs/events.jsonl. This is the sink
5
+ * for the `telemetry-log` hook, which fires on every tool call (PostToolUse,
6
+ * all tools) and on every user prompt (UserPromptSubmit) — the only vantage
7
+ * points an NLH exposes. The whole execution path (skills, sub-agent dispatch,
8
+ * knowledge-base reads, prompts) is reconstructable by reading this file.
9
+ *
10
+ * Design constraints (same discipline as activity-logger, plus one):
11
+ * - Synchronous only (hooks run in process, no async overhead)
12
+ * - Fail-silent (never throws — hooks must not be blocked by the logger)
13
+ * - APPEND-ONLY: one `appendFileSync` line per event, O(1) per call. The
14
+ * read-modify-write of a whole JSON doc (activity-logger) is O(n²) across a
15
+ * session and blows the ≤5ms budget once tool calls number in the hundreds.
16
+ * - Windows-safe paths via path.join
17
+ */
18
+
19
+ import { appendFileSync, readFileSync, mkdirSync, existsSync } from 'fs';
20
+ import { join, dirname } from 'path';
21
+
22
+ const LOGS_DIR = '.morph/logs';
23
+ export const EVENTS_FILE = join(LOGS_DIR, 'events.jsonl');
24
+
25
+ function getEventsPath(projectPath) {
26
+ return join(projectPath || process.cwd(), EVENTS_FILE);
27
+ }
28
+
29
+ /**
30
+ * Append a single telemetry event as one JSON line.
31
+ * Stamps an ISO `ts` when the caller omits one.
32
+ *
33
+ * @param {Object} event - Event payload (tool, skill/persona/target/prompt, feature, phase…)
34
+ * @param {string} [projectPath] - Project root (defaults to cwd)
35
+ */
36
+ export function appendTelemetryEvent(event, projectPath) {
37
+ try {
38
+ const eventsPath = getEventsPath(projectPath);
39
+ const dir = dirname(eventsPath);
40
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
41
+
42
+ const stamped = { ts: event?.ts || new Date().toISOString(), ...event };
43
+ appendFileSync(eventsPath, JSON.stringify(stamped) + '\n', 'utf-8');
44
+ } catch {
45
+ // Fail-silent — never block hooks
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Read the full event stream back into an array. A corrupt/truncated line
51
+ * (e.g. a killed process mid-write) is skipped, not fatal.
52
+ *
53
+ * @param {string} [projectPath]
54
+ * @returns {Object[]} events in write order (may be empty)
55
+ */
56
+ export function readTelemetryEvents(projectPath) {
57
+ try {
58
+ const eventsPath = getEventsPath(projectPath);
59
+ if (!existsSync(eventsPath)) return [];
60
+ const raw = readFileSync(eventsPath, 'utf-8');
61
+ const events = [];
62
+ for (const line of raw.split('\n')) {
63
+ const trimmed = line.trim();
64
+ if (!trimmed) continue;
65
+ try {
66
+ events.push(JSON.parse(trimmed));
67
+ } catch {
68
+ // skip a single corrupt line — don't lose the whole stream
69
+ }
70
+ }
71
+ return events;
72
+ } catch {
73
+ return [];
74
+ }
75
+ }
@@ -20,6 +20,7 @@ stacks:
20
20
  @.morph/framework/standards/ai-agents/prompts-in-db.md
21
21
  @.morph/framework/standards/ai-agents/rag-custom-pgvector.md
22
22
  @.morph/framework/standards/ai-agents/middleware-patterns.md
23
+ @.morph/framework/standards/ai-agents/observability-patterns.md
23
24
  @.morph/framework/standards/ai-agents/workflows.md
24
25
  @.morph/framework/standards/ai-agents/production.md
25
26
 
@@ -5,10 +5,18 @@
5
5
  "type": "object",
6
6
  "required": ["status", "approvalGates"],
7
7
  "properties": {
8
+ "name": {
9
+ "type": "string",
10
+ "description": "Human-readable feature name, stamped at birth by `morph-spec create` (defaults to the slug) and by the state-sync hook's implicit registration. Kept out of the schema's `required` array — adding it there would invalidate every feature.json that predates identity stamping; instead `create` enforces it at birth and `morph-spec doctor`/`list` flag its absence leniently."
11
+ },
12
+ "description": {
13
+ "type": "string",
14
+ "description": "What/why of the feature. The one identity field a human must supply — `morph-spec create` refuses a new feature without it (--description, or --request which seeds it). Absent on features born via the implicit hook path; surfaced as a warning by `doctor`/`list`, never a hard read-time rejection (see name)."
15
+ },
8
16
  "status": {
9
17
  "type": "string",
10
- "enum": ["draft", "in_progress", "completed"],
11
- "description": "Feature lifecycle status. `completed` is stamped by `morph-spec archive` together with archivedAt."
18
+ "enum": ["draft", "in_progress", "completed", "abandoned"],
19
+ "description": "Feature lifecycle status. `completed` is stamped by `morph-spec archive` together with archivedAt. `abandoned` is stamped by `morph-spec delete --archive` together with abandonedAt (dead scaffold / killed draft, kept in .morph/archive)."
12
20
  },
13
21
  "createdAt": { "type": "string", "format": "date-time" },
14
22
  "updatedAt": {
@@ -21,6 +29,11 @@
21
29
  "format": "date-time",
22
30
  "description": "Stamped by `morph-spec archive`."
23
31
  },
32
+ "abandonedAt": {
33
+ "type": "string",
34
+ "format": "date-time",
35
+ "description": "Stamped by `morph-spec delete --archive` when a dead/abandoned feature is moved to .morph/archive."
36
+ },
24
37
  "approvalGates": {
25
38
  "type": "object",
26
39
  "description": "One entry per gate. Written ONLY by `morph-spec approve`/`advance` (approvedBy: \"auto\") or the state-sync hook on a human \"aprovado\" answer (approvedBy: \"manual\").",
@@ -195,12 +208,7 @@
195
208
  },
196
209
  "additionalProperties": true
197
210
  },
198
- "activeAgents": { "type": "array", "items": { "type": "string" } },
199
211
  "checkpoints": { "type": "array" },
200
- "dispatchLog": {
201
- "type": "array",
202
- "description": "Dispatch events (capped at 50). Entries: { timestamp, phase, mode, agents: [{ id, subagentType, group, tasks }] }."
203
- },
204
212
  "trustConfig": { "type": "object" },
205
213
  "contextBundles": { "type": "array" },
206
214
  "fileChanges": { "type": "array" },
@@ -4,7 +4,7 @@ description: Guia a criação de uma landing page nova ponta-a-ponta a partir do
4
4
  version: core
5
5
  when-to-invoke: ao iniciar um novo projeto de LP com o template Umbraco genérico
6
6
  user-invokable: true
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # create-umbraco-lp
@@ -4,7 +4,7 @@ description: Migra uma landing page existente (React/Vite, Next.js estático ou
4
4
  version: core
5
5
  when-to-invoke: ao receber instrução "use /migrate-lp-to-umbraco {projeto}" do usuário, ou quando o usuário descrever necessidade de migrar uma LP para Umbraco
6
6
  user-invokable: true
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # migrate-lp-to-umbraco
@@ -4,7 +4,7 @@ description: Orquestra a execução autônoma de tasks de uma feature morph-spec
4
4
  version: core
5
5
  when-to-invoke: após Gate 2 aprovado, ao receber instrução "use /morph-apply" ou "implementar feature"
6
6
  user-invokable: true
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-apply
@@ -4,7 +4,7 @@ description: Entende o negócio, os usuários e a arquitetura antes de qualquer
4
4
  version: core
5
5
  when-to-invoke: ao receber um novo request de feature, antes de morph-plan
6
6
  user-invokable: true
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-brainstorming
@@ -4,7 +4,7 @@ description: Avalia a qualidade de uma tarefa ou fase com rubricas graduadas 0-1
4
4
  version: core
5
5
  when-to-invoke: ao completar uma tarefa ou ao finalizar uma fase de implementação
6
6
  user-invokable: false
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-eval
@@ -4,7 +4,7 @@ description: ADW cirúrgico de hotfix — resolve uma queda/bug crítico de prod
4
4
  version: core
5
5
  when-to-invoke: ao receber "/morph-hotfix {feature}", quando a morph-proposal classifica o pedido como workType hotfix (Passo 0), ou o usuário diz "produção caiu / hotfix / urgente"
6
6
  user-invokable: true
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-hotfix
@@ -28,10 +28,10 @@ Resolver uma queda ou bug crítico de produção o mais rápido possível — pr
28
28
  ### 1. Nascimento da feature
29
29
 
30
30
  ```
31
- morph-spec create {feature} --type hotfix
31
+ morph-spec create {feature} --type hotfix --description "<o incidente: o que quebrou / sintoma>"
32
32
  ```
33
33
 
34
- Carimba os gates `proposal` e `plan` como `approvedBy: "policy:hotfix"` e cria as pastas `0-proposal/`, `2-plan/`, `3-implement/` num passo só — a feature já nasce em `implement` (fase derivada é gate-aware). Se a feature já existe, siga adiante sem recriar.
34
+ Carimba `name` (o slug) + `description` (obrigatória no nascimento — descreva o incidente) e os gates `proposal`/`plan` como `approvedBy: "policy:hotfix"`, e cria as pastas `0-proposal/`, `2-plan/`, `3-implement/` num passo só — a feature já nasce em `implement` (fase derivada é gate-aware). Se a feature já existe, siga adiante sem recriar.
35
35
 
36
36
  ### 2. Scout — mapa do território (obrigatório)
37
37
 
@@ -4,7 +4,7 @@ description: Guia a execução de cada tarefa de implementação com standard e
4
4
  version: core
5
5
  when-to-invoke: ao iniciar cada task de implementação listada em tasks.json
6
6
  user-invokable: false
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-implement
@@ -4,7 +4,7 @@ description: Guia a produção dos três artefatos de planejamento de uma featur
4
4
  version: core
5
5
  when-to-invoke: ao iniciar a fase plan de uma feature, após o Gate 1 (proposal aprovado)
6
6
  user-invokable: true
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-plan
@@ -4,7 +4,7 @@ description: Orquestra a fase de proposta+plano de uma feature morph-spec. Encad
4
4
  version: core
5
5
  when-to-invoke: ao receber instrução "use /morph-proposal" do usuário, ou ao iniciar uma nova feature
6
6
  user-invokable: true
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-proposal
@@ -38,10 +38,10 @@ Antes de qualquer trabalho de negócio, classifique o pedido. Se `{feature-name}
38
38
  morph-spec create {feature-name} --request "<pedido do usuário>"
39
39
  ```
40
40
 
41
- (ou `--type <tipo>` diretamente quando o tipo é óbvio pelo pedido — ex.: o usuário já diz "isso é um hotfix").
41
+ (ou `--type <tipo> --description "<o quê/porquê>"` diretamente quando o tipo é óbvio pelo pedido — ex.: o usuário já diz "isso é um hotfix"). O `--request` **serve de semente da descrição**: toda feature nasce com `name` (o slug) e `description` (obrigatória) carimbados. Sem `--request` nem `--description`, o `create` recusa — nada de scaffolds anônimos.
42
42
 
43
43
  - **Classificação `high` confidence** — a feature nasce com o `workType` decidido pelo detector determinístico (keywords: `hotfix` — "produção caiu/urgente/incident/p0"; `bug` — "bug/corrigir/exception/regressão"; `chore` — "typo/rename/bump/config/docs/cleanup"). Prossiga para o branch correspondente abaixo.
44
- - **Exit 2** (empate `tie` ou nenhum sinal `none`) — o comando sai sem criar a feature e imprime `{ needsDecision, candidates, matched }`. **Você é o roteador**: decida o tipo entre os candidatos e re-rode com `morph-spec create {feature-name} --type <tipo>`. Só use `AskUserQuestion` para o usuário quando o pedido for genuinamente indeterminado — a maioria dos empates o roteador resolve sozinho pelo contexto da conversa.
44
+ - **Exit 2** (empate `tie` ou nenhum sinal `none`) — o comando sai sem criar a feature e imprime `{ needsDecision, candidates, matched }`. **Você é o roteador**: decida o tipo entre os candidatos e re-rode com `morph-spec create {feature-name} --type <tipo> --request "<pedido do usuário>"` (o `--request` re-passado carimba a descrição). Só use `AskUserQuestion` para o usuário quando o pedido for genuinamente indeterminado — a maioria dos empates o roteador resolve sozinho pelo contexto da conversa.
45
45
 
46
46
  **Branches por tipo** (dimensionamento completo em `MORPH.md` §2.1):
47
47
 
@@ -55,7 +55,7 @@ morph-spec create {feature-name} --request "<pedido do usuário>"
55
55
  - Se argumento ausente: perguntar via `AskUserQuestion` (1 pergunta, header "Feature").
56
56
  - Verificar `.morph/features/{feature-name}/`:
57
57
  - Se `0-proposal/proposal.md` existe e tem conteúdo: pular para passo 3.
58
- - Caso contrário: rode `morph-spec create {feature-name}` — cria `.morph/features/{feature-name}/0-proposal/` e o `feature.json` canônico (shape em `.morph/framework/schemas/feature.schema.json`) num passo só, server-side. Não crie a estrutura à mão nem dependa do nascimento implícito via hook no primeiro Write. Depois siga o passo 2.
58
+ - Caso contrário: rode `morph-spec create {feature-name} --description "<resumo do pedido do usuário>"` — cria `.morph/features/{feature-name}/0-proposal/` e o `feature.json` canônico (shape em `.morph/framework/schemas/feature.schema.json`) num passo só, server-side, com `name`+`description` carimbados. A `--description` é **obrigatória** no nascimento (ou `--request`, que a semeia). Não crie a estrutura à mão nem dependa do nascimento implícito via hook no primeiro Write. Depois siga o passo 2.
59
59
 
60
60
  ### 2. Invocar `Skill(morph-brainstorming)`
61
61
 
@@ -4,7 +4,7 @@ description: Conduz a fase de revisão e produz o review-report.md para o Gate 3
4
4
  version: core
5
5
  when-to-invoke: ao iniciar a fase review, após todas as tasks de tasks.json estarem com status `done`
6
6
  user-invokable: true
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-review
@@ -4,7 +4,7 @@ description: Varre o codebase existente, detecta stack atual, compara com o stac
4
4
  version: core
5
5
  when-to-invoke: ao iniciar uma feature em projeto brownfield (codebase já existente)
6
6
  user-invokable: true
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-stack-scan
@@ -4,7 +4,7 @@ description: Localiza e carrega um standard específico do STANDARDS.json sob de
4
4
  version: core
5
5
  when-to-invoke: quando precisar consultar um standard específico durante a implementação
6
6
  user-invokable: true
7
- cliVersion: "8.2.1"
7
+ cliVersion: "8.4.0"
8
8
  ---
9
9
 
10
10
  # morph-standards
@@ -29,7 +29,7 @@
29
29
  "mcp": "#mcp-integration-model-context-protocol",
30
30
  "observability": "#observability--opentelemetry"
31
31
  },
32
- "digest": "Production panorama for MAF agents. Middleware (3 levels) and MCP integration are summarized here and treated canonically in ai-agents-middleware-patterns / ai-agents-mcp-tools / ai-agents-mcp-server. Owns: A2A protocol via Microsoft.Agents.AI.A2A; Redis semantic + HybridCache caching; OpenTelemetry observability with AgentOpenTelemetryConsts.DefaultSourceName; security and production checklists."
32
+ "digest": "Production panorama for MAF agents. Middleware (3 levels), MCP integration, and observability/OpenTelemetry are summarized here and treated canonically in ai-agents-middleware-patterns / ai-agents-mcp-tools / ai-agents-mcp-server / ai-agents-observability-patterns. Owns: A2A protocol via Microsoft.Agents.AI.A2A; Redis semantic + HybridCache caching; security and production checklists."
33
33
  },
34
34
  {
35
35
  "id": "ai-agents-setup",
@@ -309,6 +309,40 @@
309
309
  },
310
310
  "digest": "Three middleware levels intercept agent behavior: Agent Run (whole invocation), Function Calling (individual tool calls), IChatClient (raw LLM calls). Register via .AsBuilder().Use(...). Patterns: telemetry, resilience (retry/rate-limit), anti-hallucination (validate tool args before next())."
311
311
  },
312
+ {
313
+ "id": "ai-agents-observability-patterns",
314
+ "name": "observability patterns",
315
+ "path": "ai-agents/observability-patterns.md",
316
+ "category": "ai-agents",
317
+ "subcategory": null,
318
+ "tags": [
319
+ "ai-agents",
320
+ "maf",
321
+ "observability"
322
+ ],
323
+ "stacks": [
324
+ "dotnet"
325
+ ],
326
+ "aliases": [
327
+ "observability",
328
+ "opentelemetry",
329
+ "telemetry",
330
+ "logging",
331
+ "tracing",
332
+ "log investigation",
333
+ "troubleshooting",
334
+ "aspire dashboard",
335
+ "correlation id",
336
+ "agent observability",
337
+ "agent telemetry"
338
+ ],
339
+ "anchors": {
340
+ "setup": "#setup--opentelemetry",
341
+ "enrichment": "#enrichment--o-que-preencher-em-spans-e-logs",
342
+ "investigation": "#investigação-e-análise--quando-algo-dá-errado"
343
+ },
344
+ "digest": "OpenTelemetry setup for MAF agents (AgentOpenTelemetryConsts.DefaultSourceName, traces+metrics+logging). Enrichment: correlation_id propagation across async boundaries, gen_ai.* span attributes, structured ILogger scopes, sensitive-data logging rules. Investigation workflow for production incidents: anchor on correlation_id/time window, drill from root span to children, check metrics before theorizing, reproduce with Debug in staging (never production)."
345
+ },
312
346
  {
313
347
  "id": "ai-agents-modalities-vision",
314
348
  "name": "modalities vision",
@@ -1756,6 +1790,14 @@
1756
1790
  "ai-agents-production",
1757
1791
  "ai-agents-middleware-patterns"
1758
1792
  ],
1793
+ "agent observability": [
1794
+ "ai-agents-production",
1795
+ "ai-agents-observability-patterns"
1796
+ ],
1797
+ "agent telemetry": [
1798
+ "ai-agents-production",
1799
+ "ai-agents-observability-patterns"
1800
+ ],
1759
1801
  "when not to use workflow": [
1760
1802
  "ai-agents-workflows",
1761
1803
  "ai-agents-sweet-spot"