@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.
- package/README.md +73 -102
- package/bin/morph-spec.js +36 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +77 -134
- package/docs/QUICKSTART.md +47 -57
- package/framework/CLAUDE.md +6 -3
- package/framework/MORPH.md +2 -2
- package/framework/agents.json +3 -1
- package/framework/commands/morph-hotfix.md +1 -1
- package/framework/hooks/README.md +3 -0
- package/framework/hooks/claude-code/core/state-sync.js +41 -9
- package/framework/hooks/claude-code/core/telemetry-log.js +102 -0
- package/framework/hooks/shared/state-writer.js +14 -3
- package/framework/hooks/shared/telemetry-logger.js +75 -0
- package/framework/rules/ai-agents-standards.md +1 -0
- package/framework/schemas/feature.schema.json +15 -7
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +1 -1
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- package/framework/skills/core/morph-implement/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +1 -1
- package/framework/skills/core/morph-proposal/SKILL.md +4 -4
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +43 -1
- package/framework/standards/ai-agents/observability-patterns.md +156 -0
- package/framework/standards/ai-agents/production.md +3 -47
- package/framework/templates/REGISTRY.json +0 -93
- package/package.json +1 -1
- package/src/commands/observability/telemetry.js +75 -0
- package/src/commands/project/doctor.js +28 -0
- package/src/commands/state/advance.js +17 -0
- package/src/commands/state/approve.js +13 -0
- package/src/commands/state/create.js +21 -1
- package/src/commands/state/delete.js +143 -0
- package/src/commands/state/list.js +110 -0
- package/src/core/state/feature-identity.js +36 -0
- package/src/core/state/gate-order.js +51 -0
- package/src/core/state/lifecycle-audit.js +79 -0
- package/src/core/state/state-manager.js +47 -140
- package/src/dashboard/app/pages/LiveState.jsx +0 -43
- package/src/lib/detectors/design-system-detector.js +0 -43
- package/src/lib/generators/index.js +0 -1
- package/src/lib/telemetry/telemetry-reader.js +103 -0
- package/src/lib/validators/validation-runner.js +9 -73
- package/src/utils/hooks-installer.js +27 -3
- package/framework/templates/context/CONTEXT-FEATURE.md +0 -269
- package/framework/templates/context/CONTEXT.md +0 -181
- package/src/lib/agents/metrics-aggregator.js +0 -56
- package/src/lib/agents/metrics-calculator.js +0 -125
- package/src/lib/generators/metadata-extractor.js +0 -385
|
@@ -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
|
}
|
|
@@ -150,49 +150,6 @@ export function detectDesignSystem(projectPath, featureName = null) {
|
|
|
150
150
|
return result;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
/**
|
|
154
|
-
* Check if UI agents are active for a feature
|
|
155
|
-
* @param {string} projectPath - Root path of the project
|
|
156
|
-
* @param {string} featureName - Feature name
|
|
157
|
-
* @returns {boolean} True if UI agents are active
|
|
158
|
-
*/
|
|
159
|
-
export function hasUIAgentsActive(projectPath, featureName) {
|
|
160
|
-
try {
|
|
161
|
-
const statePath = join(projectPath, '.morph/state.json');
|
|
162
|
-
|
|
163
|
-
if (!existsSync(statePath)) {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
168
|
-
|
|
169
|
-
// Handle both array and object format
|
|
170
|
-
let feature;
|
|
171
|
-
if (Array.isArray(state.features)) {
|
|
172
|
-
feature = state.features.find(f => f.name === featureName);
|
|
173
|
-
} else if (state.features && typeof state.features === 'object') {
|
|
174
|
-
feature = state.features[featureName];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (!feature || !feature.activeAgents) {
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// UI design agents that require a design system before implement phase.
|
|
182
|
-
// Implementation-only agents (nextjs-expert, css-specialist)
|
|
183
|
-
// build from existing designs and do NOT trigger this gate.
|
|
184
|
-
const uiAgents = [
|
|
185
|
-
'ui-designer',
|
|
186
|
-
'uiux-designer',
|
|
187
|
-
'ui-ux-designer',
|
|
188
|
-
];
|
|
189
|
-
|
|
190
|
-
return feature.activeAgents.some(agentId => uiAgents.includes(agentId));
|
|
191
|
-
} catch {
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
153
|
/**
|
|
197
154
|
* Get recommended design system locations based on project type
|
|
198
155
|
* @param {string} projectPath - Root path of the project
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry aggregation — reads the append-only harness event stream
|
|
3
|
+
* (.morph/logs/events.jsonl, written by the telemetry-log hook) and summarizes
|
|
4
|
+
* the whole execution path: which skills ran, which personas were dispatched,
|
|
5
|
+
* which standards (knowledge base) were read, the tool mix, and the prompts.
|
|
6
|
+
*
|
|
7
|
+
* Pure aggregation (`aggregateTelemetry`) is separated from disk I/O
|
|
8
|
+
* (`loadTelemetry`) so the summary logic is unit-testable without fixtures —
|
|
9
|
+
* the same split fleet-scanner uses. CLI-side; runs in the target project cwd.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync, existsSync } from 'fs';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
|
|
15
|
+
const EVENTS_FILE = join('.morph', 'logs', 'events.jsonl');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Read the raw event stream from a project root. A corrupt/truncated line is
|
|
19
|
+
* skipped, not fatal. Returns [] when there is no log.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} [projectPath]
|
|
22
|
+
* @returns {Object[]}
|
|
23
|
+
*/
|
|
24
|
+
export function loadTelemetry(projectPath) {
|
|
25
|
+
try {
|
|
26
|
+
const p = join(projectPath || process.cwd(), EVENTS_FILE);
|
|
27
|
+
if (!existsSync(p)) return [];
|
|
28
|
+
const events = [];
|
|
29
|
+
for (const line of readFileSync(p, 'utf-8').split('\n')) {
|
|
30
|
+
const trimmed = line.trim();
|
|
31
|
+
if (!trimmed) continue;
|
|
32
|
+
try { events.push(JSON.parse(trimmed)); } catch { /* skip corrupt line */ }
|
|
33
|
+
}
|
|
34
|
+
return events;
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function rank(map) {
|
|
41
|
+
return [...map.entries()]
|
|
42
|
+
.map(([name, count]) => ({ name, count }))
|
|
43
|
+
.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Summarize an event stream.
|
|
48
|
+
*
|
|
49
|
+
* @param {Object[]} events - raw events (from loadTelemetry)
|
|
50
|
+
* @param {{ feature?: string }} [opts] - optional feature filter
|
|
51
|
+
* @returns {{
|
|
52
|
+
* total: number,
|
|
53
|
+
* skills: Array<{name:string,count:number}>,
|
|
54
|
+
* personas: Array<{name:string,count:number}>,
|
|
55
|
+
* standards: Array<{path:string,count:number}>,
|
|
56
|
+
* tools: Array<{name:string,count:number}>,
|
|
57
|
+
* prompts: Array<{ts:string,source:'user'|'agent',feature:?string,phase:?string,text:string}>,
|
|
58
|
+
* features: string[]
|
|
59
|
+
* }}
|
|
60
|
+
*/
|
|
61
|
+
export function aggregateTelemetry(events, opts = {}) {
|
|
62
|
+
const scoped = opts.feature
|
|
63
|
+
? (events || []).filter(e => e.feature === opts.feature)
|
|
64
|
+
: (events || []);
|
|
65
|
+
|
|
66
|
+
const skills = new Map();
|
|
67
|
+
const personas = new Map();
|
|
68
|
+
const standards = new Map();
|
|
69
|
+
const tools = new Map();
|
|
70
|
+
const features = new Set();
|
|
71
|
+
const prompts = [];
|
|
72
|
+
|
|
73
|
+
for (const e of scoped) {
|
|
74
|
+
if (e.tool) tools.set(e.tool, (tools.get(e.tool) || 0) + 1);
|
|
75
|
+
if (e.feature) features.add(e.feature);
|
|
76
|
+
|
|
77
|
+
if (e.tool === 'Skill' && e.skill) {
|
|
78
|
+
skills.set(e.skill, (skills.get(e.skill) || 0) + 1);
|
|
79
|
+
} else if ((e.tool === 'Agent' || e.tool === 'Task') && e.persona) {
|
|
80
|
+
personas.set(e.persona, (personas.get(e.persona) || 0) + 1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (e.kind === 'standard' && e.target) {
|
|
84
|
+
standards.set(e.target, (standards.get(e.target) || 0) + 1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (e.tool === 'UserPrompt' && typeof e.prompt === 'string') {
|
|
88
|
+
prompts.push({ ts: e.ts, source: 'user', feature: e.feature ?? null, phase: e.phase ?? null, text: e.prompt });
|
|
89
|
+
} else if ((e.tool === 'Agent' || e.tool === 'Task') && typeof e.prompt === 'string' && e.prompt) {
|
|
90
|
+
prompts.push({ ts: e.ts, source: 'agent', feature: e.feature ?? null, phase: e.phase ?? null, text: e.prompt });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
total: scoped.length,
|
|
96
|
+
skills: rank(skills),
|
|
97
|
+
personas: rank(personas),
|
|
98
|
+
standards: rank(standards).map(({ name, count }) => ({ path: name, count })),
|
|
99
|
+
tools: rank(tools),
|
|
100
|
+
prompts,
|
|
101
|
+
features: [...features],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MORPH-SPEC Validation Runner
|
|
3
3
|
*
|
|
4
|
-
* Unified orchestrator that runs the right validators based on the
|
|
5
|
-
*
|
|
4
|
+
* Unified orchestrator that runs the right validators based on the project's
|
|
5
|
+
* stack profile. Called by task-done before marking tasks complete.
|
|
6
6
|
*
|
|
7
7
|
* MORPH-SPEC 3.0 - Phase A: Active Validation Pipeline
|
|
8
8
|
*/
|
|
@@ -10,16 +10,10 @@
|
|
|
10
10
|
import { existsSync, readFileSync } from 'fs';
|
|
11
11
|
import { join } from 'path';
|
|
12
12
|
import chalk from 'chalk';
|
|
13
|
-
import { loadState } from '../../core/state/state-manager.js';
|
|
14
13
|
import { getAbsoluteOutputPath, resolveContractsOutputType } from '../../core/paths/output-schema.js';
|
|
15
14
|
import { getStackProfile } from '../stack/stack-profile.js';
|
|
16
15
|
import { aggregateScores, loadRubric } from './score-aggregator.js';
|
|
17
16
|
|
|
18
|
-
/** Validators that only make sense for .NET projects. */
|
|
19
|
-
const DOTNET_ONLY_VALIDATORS = new Set([
|
|
20
|
-
'architecture', 'packages', 'contract-compliance'
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
17
|
/**
|
|
24
18
|
* Heavy validators skipped in 'light' mode (checkpoint-only).
|
|
25
19
|
* Light mode runs only spec, content, and stack-specific lightweight validators.
|
|
@@ -29,31 +23,6 @@ const HEAVY_VALIDATORS = new Set([
|
|
|
29
23
|
'css', 'design-system'
|
|
30
24
|
]);
|
|
31
25
|
|
|
32
|
-
/**
|
|
33
|
-
* Load agent → validators map from agents.json (data-driven)
|
|
34
|
-
* @param {string} projectPath - Root path of the project
|
|
35
|
-
* @returns {Object} Map of agent IDs to validator arrays
|
|
36
|
-
*/
|
|
37
|
-
function loadAgentValidatorMap(projectPath) {
|
|
38
|
-
try {
|
|
39
|
-
const agentsPath = join(projectPath, '.morph/framework/agents.json');
|
|
40
|
-
const agentsConfig = JSON.parse(readFileSync(agentsPath, 'utf8'));
|
|
41
|
-
|
|
42
|
-
const map = {};
|
|
43
|
-
Object.entries(agentsConfig.agents || {}).forEach(([agentId, agent]) => {
|
|
44
|
-
if (!agentId.startsWith('_comment') && agent.validators && agent.validators.length > 0) {
|
|
45
|
-
map[agentId] = agent.validators;
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
return map;
|
|
50
|
-
} catch (err) {
|
|
51
|
-
console.warn(chalk.yellow('⚠️ Failed to load agents.json - no validators will run'));
|
|
52
|
-
console.warn(chalk.gray(` Run 'morph-spec init' to set up MORPH-SPEC configuration`));
|
|
53
|
-
return {}; // Empty map = honest failure
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
26
|
/**
|
|
58
27
|
* Run all relevant validators for a feature
|
|
59
28
|
*
|
|
@@ -61,7 +30,7 @@ function loadAgentValidatorMap(projectPath) {
|
|
|
61
30
|
* @param {string} featureName - Feature name from state.json
|
|
62
31
|
* @param {Object} options - Options
|
|
63
32
|
* @param {boolean} options.verbose - Show detailed output
|
|
64
|
-
* @param {string[]} options.validators - Specific validators to run (overrides
|
|
33
|
+
* @param {string[]} options.validators - Specific validators to run (overrides stack-based detection)
|
|
65
34
|
* @returns {Object} { passed, errors, warnings, issues, summary }
|
|
66
35
|
*/
|
|
67
36
|
export async function runValidation(projectPath, featureName, options = {}) {
|
|
@@ -81,7 +50,7 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
81
50
|
};
|
|
82
51
|
|
|
83
52
|
// Determine which validators to run
|
|
84
|
-
const validatorIds = options.validators || detectValidators(
|
|
53
|
+
const validatorIds = options.validators || detectValidators(projectPath);
|
|
85
54
|
|
|
86
55
|
// Filter by mode: 'light' skips heavy validators, 'full' (default) runs all
|
|
87
56
|
const mode = options.mode || 'full';
|
|
@@ -98,17 +67,8 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
98
67
|
validatorIds.push(...constraints.enabledValidators);
|
|
99
68
|
|
|
100
69
|
if (validatorIds.length === 0) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const activeAgents = state?.features?.[featureName]?.activeAgents || [];
|
|
104
|
-
if (activeAgents.length === 0) {
|
|
105
|
-
result.summary = 'No validators ran — feature has no active agents';
|
|
106
|
-
console.log(chalk.gray('\n ℹ️ No validators configured (no active agents in state.json)'));
|
|
107
|
-
console.log(chalk.gray(' Add agents: morph-spec state add-agent ' + featureName + ' <agent-id>'));
|
|
108
|
-
} else {
|
|
109
|
-
result.summary = `No validators mapped for active agents: ${activeAgents.join(', ')}`;
|
|
110
|
-
console.log(chalk.gray(`\n ℹ️ Agents [${activeAgents.join(', ')}] have no validators in agents.json`));
|
|
111
|
-
}
|
|
70
|
+
result.summary = 'No validators ran — no validators configured for this stack';
|
|
71
|
+
console.log(chalk.gray('\n ℹ️ No validators configured for this stack'));
|
|
112
72
|
result._skipReason = result.summary;
|
|
113
73
|
return result;
|
|
114
74
|
}
|
|
@@ -191,37 +151,13 @@ export async function runValidation(projectPath, featureName, options = {}) {
|
|
|
191
151
|
}
|
|
192
152
|
|
|
193
153
|
/**
|
|
194
|
-
* Detect which validators to run
|
|
195
|
-
* @param {string} featureName - Feature name
|
|
154
|
+
* Detect which validators to run, driven by the project's stack profile.
|
|
196
155
|
* @param {string} projectPath - Project root path (default: current directory)
|
|
197
156
|
* @returns {string[]} Array of validator IDs to run
|
|
198
157
|
*/
|
|
199
|
-
function detectValidators(
|
|
158
|
+
function detectValidators(projectPath = '.') {
|
|
200
159
|
const profile = getStackProfile(projectPath);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (!state || !state.features[featureName]) {
|
|
204
|
-
return [...profile.defaultValidators]; // stack-aware defaults
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const feature = state.features[featureName];
|
|
208
|
-
const agents = feature.activeAgents || [];
|
|
209
|
-
const validatorSet = new Set(profile.defaultValidators); // stack defaults first
|
|
210
|
-
|
|
211
|
-
const agentValidatorMap = loadAgentValidatorMap(projectPath);
|
|
212
|
-
for (const agentId of agents) {
|
|
213
|
-
const validators = agentValidatorMap[agentId] || [];
|
|
214
|
-
validators.forEach(v => validatorSet.add(v));
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Filter out .NET-only validators for non-.NET projects
|
|
218
|
-
if (!profile.isDotnet) {
|
|
219
|
-
for (const v of DOTNET_ONLY_VALIDATORS) {
|
|
220
|
-
validatorSet.delete(v);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return Array.from(validatorSet);
|
|
160
|
+
return [...profile.defaultValidators];
|
|
225
161
|
}
|
|
226
162
|
|
|
227
163
|
/**
|
|
@@ -15,7 +15,7 @@ import { homedir } from 'os';
|
|
|
15
15
|
import { execSync } from 'child_process';
|
|
16
16
|
|
|
17
17
|
/** Current hooks schema version — bump when hook definitions change */
|
|
18
|
-
const HOOKS_VERSION = '3.
|
|
18
|
+
const HOOKS_VERSION = '3.4.0';
|
|
19
19
|
|
|
20
20
|
/** Marker for old dispatch.js (v1) */
|
|
21
21
|
const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
@@ -165,13 +165,37 @@ const MORPH_HOOKS = [
|
|
|
165
165
|
]
|
|
166
166
|
},
|
|
167
167
|
|
|
168
|
-
// === PostToolUse: All — loop detection ===
|
|
168
|
+
// === PostToolUse: All — telemetry capture (silent) + loop detection ===
|
|
169
|
+
// telemetry-log is the single generic vantage point of an NLH: it appends
|
|
170
|
+
// every tool call (skill, sub-agent dispatch, knowledge-base read, …) to
|
|
171
|
+
// .morph/logs/events.jsonl. Silent and fail-open — never blocks, never
|
|
172
|
+
// injects. `morph-spec telemetry` reads the stream back.
|
|
169
173
|
{
|
|
170
174
|
event: 'PostToolUse',
|
|
171
175
|
matcher: null,
|
|
176
|
+
hooks: [
|
|
177
|
+
{
|
|
178
|
+
type: 'command',
|
|
179
|
+
command: 'node framework/hooks/claude-code/core/telemetry-log.js',
|
|
180
|
+
timeout: 5000
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
type: 'command',
|
|
184
|
+
command: 'node framework/hooks/claude-code/core/loop-detect.js',
|
|
185
|
+
timeout: 5000
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
// === UserPromptSubmit — telemetry capture of the user's prompt (silent) ===
|
|
191
|
+
// The only vantage point on the user's own prompt; completes the execution
|
|
192
|
+
// path alongside sub-agent prompts captured on PostToolUse.
|
|
193
|
+
{
|
|
194
|
+
event: 'UserPromptSubmit',
|
|
195
|
+
matcher: null,
|
|
172
196
|
hooks: [{
|
|
173
197
|
type: 'command',
|
|
174
|
-
command: 'node framework/hooks/claude-code/core/
|
|
198
|
+
command: 'node framework/hooks/claude-code/core/telemetry-log.js',
|
|
175
199
|
timeout: 5000
|
|
176
200
|
}]
|
|
177
201
|
},
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
# {{FEATURE_NAME}} - Feature Context
|
|
2
|
-
|
|
3
|
-
> Auto-generated by MORPH-SPEC Framework
|
|
4
|
-
> Last updated: {{TIMESTAMP}}
|
|
5
|
-
|
|
6
|
-
## Feature Overview
|
|
7
|
-
|
|
8
|
-
**Name:** {{FEATURE_NAME}}
|
|
9
|
-
**Phase:** {{PHASE}}
|
|
10
|
-
**Status:** {{STATUS}}
|
|
11
|
-
**Complexity:** {{COMPLEXITY}}
|
|
12
|
-
|
|
13
|
-
{{#if DESCRIPTION}}
|
|
14
|
-
{{DESCRIPTION}}
|
|
15
|
-
{{/if}}
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Progress Tracking
|
|
20
|
-
|
|
21
|
-
### Current Phase: {{PHASE}}
|
|
22
|
-
|
|
23
|
-
{{PHASE_DESCRIPTION}}
|
|
24
|
-
|
|
25
|
-
**Completed Phases:**
|
|
26
|
-
{{#each COMPLETED_PHASES}}
|
|
27
|
-
- ✅ {{name}} - {{completedAt}}
|
|
28
|
-
{{/each}}
|
|
29
|
-
|
|
30
|
-
**Next Phases:**
|
|
31
|
-
{{#each UPCOMING_PHASES}}
|
|
32
|
-
- ⏭️ {{name}}
|
|
33
|
-
{{/each}}
|
|
34
|
-
|
|
35
|
-
### Task Progress
|
|
36
|
-
|
|
37
|
-
**Total Tasks:** {{TASKS_TOTAL}}
|
|
38
|
-
**Completed:** {{TASKS_COMPLETED}} ({{TASKS_PROGRESS}}%)
|
|
39
|
-
**In Progress:** {{TASKS_IN_PROGRESS}}
|
|
40
|
-
**Blocked:** {{TASKS_BLOCKED}}
|
|
41
|
-
|
|
42
|
-
{{#if CURRENT_TASK}}
|
|
43
|
-
**Current Task:** {{CURRENT_TASK.id}} - {{CURRENT_TASK.title}}
|
|
44
|
-
{{/if}}
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## Active Agents ({{ACTIVE_AGENTS_COUNT}})
|
|
49
|
-
|
|
50
|
-
### Team Structure
|
|
51
|
-
|
|
52
|
-
{{#if TEAM_LEAD}}
|
|
53
|
-
**Team Lead:** {{TEAM_LEAD.title}} (`{{TEAM_LEAD.id}}`)
|
|
54
|
-
{{/if}}
|
|
55
|
-
|
|
56
|
-
{{#each SQUADS}}
|
|
57
|
-
### {{leader.title}} Squad
|
|
58
|
-
**Leader:** `{{leader.id}}`
|
|
59
|
-
**Members ({{members.length}}):**
|
|
60
|
-
{{#each members}}
|
|
61
|
-
- {{title}} (`{{id}}`) {{#if status}}[{{status}}]{{/if}}
|
|
62
|
-
{{/each}}
|
|
63
|
-
{{/each}}
|
|
64
|
-
|
|
65
|
-
### Validators (Run in Hooks)
|
|
66
|
-
{{#each VALIDATORS}}
|
|
67
|
-
- {{title}} (`{{id}}`)
|
|
68
|
-
{{/each}}
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## Agent Teams
|
|
73
|
-
|
|
74
|
-
{{#if AGENT_TEAMS_DECISION}}
|
|
75
|
-
{{#if AGENT_TEAMS_DECISION.useAgentTeams}}
|
|
76
|
-
✅ **Agent Teams Recommended**
|
|
77
|
-
|
|
78
|
-
**Reason:** {{AGENT_TEAMS_DECISION.reason}}
|
|
79
|
-
**Display Mode:** {{AGENT_TEAMS_DECISION.displayMode}}
|
|
80
|
-
**Total Teammates:** {{AGENT_TEAMS_DECISION.totalTeammates}}
|
|
81
|
-
|
|
82
|
-
**Team Composition:**
|
|
83
|
-
- Team Lead: {{AGENT_TEAMS_DECISION.teamLead}}
|
|
84
|
-
- Domain Leaders: {{AGENT_TEAMS_DECISION.squads}}
|
|
85
|
-
- Validators (hooks): {{AGENT_TEAMS_DECISION.validators}}
|
|
86
|
-
|
|
87
|
-
{{else}}
|
|
88
|
-
❌ **Single Session Recommended**
|
|
89
|
-
|
|
90
|
-
**Reason:** {{AGENT_TEAMS_DECISION.reason}}
|
|
91
|
-
**Recommendation:** {{AGENT_TEAMS_DECISION.recommendation}}
|
|
92
|
-
{{/if}}
|
|
93
|
-
{{/if}}
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## Outputs Generated
|
|
98
|
-
|
|
99
|
-
{{#each OUTPUTS}}
|
|
100
|
-
### {{type}}
|
|
101
|
-
**Status:** {{#if generated}}✅ Generated{{else}}⏳ Pending{{/if}}
|
|
102
|
-
{{#if path}}**Path:** `{{path}}`{{/if}}
|
|
103
|
-
{{#if generatedAt}}**Generated:** {{generatedAt}}{{/if}}
|
|
104
|
-
{{/each}}
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## Decisions Log
|
|
109
|
-
|
|
110
|
-
{{#each DECISIONS}}
|
|
111
|
-
### ADR-{{id}}: {{title}}
|
|
112
|
-
|
|
113
|
-
**Status:** {{status}}
|
|
114
|
-
**Date:** {{date}}
|
|
115
|
-
**Decided By:** {{decidedBy}}
|
|
116
|
-
|
|
117
|
-
**Context:** {{context}}
|
|
118
|
-
|
|
119
|
-
**Decision:** {{decision}}
|
|
120
|
-
|
|
121
|
-
{{#if constraints}}
|
|
122
|
-
**Constraints for Validators:**
|
|
123
|
-
```json
|
|
124
|
-
{{constraints}}
|
|
125
|
-
```
|
|
126
|
-
{{/if}}
|
|
127
|
-
|
|
128
|
-
{{#if alternatives}}
|
|
129
|
-
**Alternatives Considered:**
|
|
130
|
-
{{#each alternatives}}
|
|
131
|
-
- {{name}}: {{rationale}}
|
|
132
|
-
{{/each}}
|
|
133
|
-
{{/if}}
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
{{/each}}
|
|
137
|
-
|
|
138
|
-
{{#unless DECISIONS}}
|
|
139
|
-
No decisions documented yet. Decisions will be logged in `decisions.md` during Design phase.
|
|
140
|
-
{{/unless}}
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## Validation Status
|
|
145
|
-
|
|
146
|
-
{{#if VALIDATION_RESULTS}}
|
|
147
|
-
### Latest Validation ({{VALIDATION_TIMESTAMP}})
|
|
148
|
-
|
|
149
|
-
**Overall:** {{#if VALIDATION_PASSED}}✅ PASSED{{else}}❌ FAILED{{/if}}
|
|
150
|
-
|
|
151
|
-
{{#each VALIDATION_RESULTS}}
|
|
152
|
-
#### {{validator}}
|
|
153
|
-
**Status:** {{#if passed}}✅ PASSED{{else}}❌ FAILED{{/if}}
|
|
154
|
-
{{#if issues}}
|
|
155
|
-
**Issues ({{issues.length}}):**
|
|
156
|
-
{{#each issues}}
|
|
157
|
-
- [{{severity}}] {{message}} ({{file}}:{{line}})
|
|
158
|
-
{{/each}}
|
|
159
|
-
{{/if}}
|
|
160
|
-
{{/each}}
|
|
161
|
-
|
|
162
|
-
{{else}}
|
|
163
|
-
No validation runs yet. Validators run automatically via checkpoints.
|
|
164
|
-
{{/if}}
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## Tasks Breakdown
|
|
169
|
-
|
|
170
|
-
{{#each TASKS}}
|
|
171
|
-
### {{id}}: {{title}}
|
|
172
|
-
|
|
173
|
-
**Status:** {{status}}
|
|
174
|
-
{{#if priority}}**Priority:** {{priority}}{{/if}}
|
|
175
|
-
{{#if estimatedFiles}}**Estimated Files:** {{estimatedFiles}}{{/if}}
|
|
176
|
-
{{#if dependencies}}**Dependencies:** {{dependencies}}{{/if}}
|
|
177
|
-
|
|
178
|
-
{{#if description}}
|
|
179
|
-
{{description}}
|
|
180
|
-
{{/if}}
|
|
181
|
-
|
|
182
|
-
{{#if agent}}**Assigned Agent:** `{{agent}}`{{/if}}
|
|
183
|
-
|
|
184
|
-
{{#if checkpointAfter}}🔖 **Checkpoint after this task**{{/if}}
|
|
185
|
-
|
|
186
|
-
{{#if completedAt}}✅ Completed: {{completedAt}}{{/if}}
|
|
187
|
-
{{#if blockedReason}}❌ Blocked: {{blockedReason}}{{/if}}
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
{{/each}}
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## Checkpoints
|
|
195
|
-
|
|
196
|
-
{{#each CHECKPOINTS}}
|
|
197
|
-
### {{timestamp}}
|
|
198
|
-
**Phase:** {{phase}}
|
|
199
|
-
**Tasks Completed:** {{tasksCompleted}}/{{tasksTotal}}
|
|
200
|
-
|
|
201
|
-
{{#if note}}
|
|
202
|
-
**Note:** {{note}}
|
|
203
|
-
{{/if}}
|
|
204
|
-
|
|
205
|
-
{{#if validation}}
|
|
206
|
-
**Validation:** {{validation.passed}}/{{validation.total}} checks passed
|
|
207
|
-
{{#if validation.issues}}
|
|
208
|
-
- Issues: {{validation.issues}}
|
|
209
|
-
{{/if}}
|
|
210
|
-
{{/if}}
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
{{/each}}
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## Related Standards
|
|
218
|
-
|
|
219
|
-
{{#each RELATED_STANDARDS}}
|
|
220
|
-
### {{name}}
|
|
221
|
-
**Path:** `{{path}}`
|
|
222
|
-
{{#if relevantSections}}
|
|
223
|
-
**Relevant Sections:** {{relevantSections}}
|
|
224
|
-
{{/if}}
|
|
225
|
-
{{/each}}
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
|
|
229
|
-
## Quick Actions
|
|
230
|
-
|
|
231
|
-
### Continue Implementation
|
|
232
|
-
During the implement phase, track your work with the native `TaskCreate`/`TaskUpdate`
|
|
233
|
-
tools. Persist progress by writing `tasks.json` (`2-plan/tasks.json`), touching ONLY
|
|
234
|
-
the mutable fields `status`, `outputs`, and `notes` — via the `Write` tool or an `Edit`
|
|
235
|
-
that touches only those fields. The plan contract (`title`, `description`,
|
|
236
|
-
`dependencies`, `effort`, `doneCriteria`, `group`) is frozen after Gate 2. The recap is
|
|
237
|
-
written by the implement skill; state syncs automatically via the `state-sync` hook.
|
|
238
|
-
|
|
239
|
-
### Advance Phase
|
|
240
|
-
```bash
|
|
241
|
-
# Approve the current gate and scaffold the next phase folder in one step
|
|
242
|
-
morph-spec advance {{FEATURE_NAME}}
|
|
243
|
-
|
|
244
|
-
# Validate feature outputs
|
|
245
|
-
morph-spec validate-feature {{FEATURE_NAME}}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### Validation
|
|
249
|
-
```bash
|
|
250
|
-
# Validate this feature's outputs against active standards
|
|
251
|
-
morph-spec validate-feature {{FEATURE_NAME}}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
## File Paths
|
|
257
|
-
|
|
258
|
-
- **Outputs Directory:** `.morph/features/{{FEATURE_NAME}}/`
|
|
259
|
-
- **Proposal:** `{{PROPOSAL_PATH}}`
|
|
260
|
-
- **Spec:** `{{SPEC_PATH}}`
|
|
261
|
-
- **Contracts:** `{{CONTRACTS_PATH}}`
|
|
262
|
-
- **Tasks:** `{{TASKS_PATH}}`
|
|
263
|
-
- **Decisions:** `{{DECISIONS_PATH}}`
|
|
264
|
-
- **Recap:** `{{RECAP_PATH}}`
|
|
265
|
-
|
|
266
|
-
---
|
|
267
|
-
|
|
268
|
-
*Generated by MORPH-SPEC Framework v{{MORPH_VERSION}}*
|
|
269
|
-
*Feature ID: {{FEATURE_ID}}*
|